Audit · firm’s term: audit report
QuillAudits reviewed Quranium on 15 Nov 2024: 19 findings, 0 critical; 14 marked fixed, 5 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 | 0 | 0 | 6 |
| Medium | 2 | 0 | 6 |
| Low | 3 | 0 | 2 |
_advanceTier() is called to increment the tier when all the nodes in the current tier have been sold. When this happens, the tierMinted is reset to 0 but this doesn't account for when nodes in the new tier are bought. tierMinted should reflect the amount of nodes purchased in the new tier for proper accounting purposes else when users try to call buyNodes on the next call, the value returned will make the available nodes in the current tier always return a wrong value.
In the `buyNode` function, when handling purchases of nodes that have referrals, the function accurately calculates the amount the referrer should get. However, it tries to do the token transfer to the referrer immediately,without first transferring in rewards from the buyer. This causes the function to revert because the contract does not yet have the tokens as at the point when the transfer is being attempted. Hence, for the first transaction for a token(usdt/usdc/wbtc) on the contract , and everytime the admin withdraws token funds from the contract, this function call will revert.
When _simulatePurchase() is called the pricePerNode variable doesn't get updated after the current tier's slot is completely filled. As remainingQuantity > availableInCurrentTier, the loop restarts using the same price as before because getTokenPrice checks using the tier price using the currentTier variable which doesn't get updated as the loop exits (simulatedTier is incremented but isn't used in the getTokenPrice check). This allows users to purchase up to the maximum allocation per user with the same price of tokens across different tiers, leading to economic loss for the protocol.
The `referralRewards` being used to accumulate rewards for users has a critical bug rendering it useless.The mapping adds all reward cuts directly, It does not handle the different tokens separately. Take for example: User uses a referrerCode and purchases 5 NFTs with eth, This records 70000000000000000 which is 0.07 ether as rewards for the referrer. When another user comes to purchase 5 more NFTs in tier one, using that same referrer's code but purchased with USDC, the function adds 215803115 to that referrers balance, which is 215 USDC. But now, the referrers reward is now `70000000215803115`, which cannot be valued in usdc or ether or wbtc.
The QNodeKey contract automatically switches tiers once one tier is filled up using ``_advanceTier()``. This resets the tier counter, and change the current Tier to the next one, which is always more expensive and might have a smaller size. However when this change occurs, either in between an order (A buyNode function call that will finish a current tier and start a new one) or at the start of a new order( A buyNode function call that is starting a new Tier apart from Tier 1 ), the `buyNode()` function uses the older price when calculating the price addition of the new Tier. This is because the price of the previous Tier has been cached in `pricePerNode` which is then used through the whole calculation.
The contract utilizes two mappings to handle the TierSizes and TierPrices to handle the current Tier size limit and price.The current MAX_TIERS is set to 77, however this value can be updated using the `setMaxTiers()`, but this function only changes the Max possible number of tiers. It does not update the necessary values for both mappings. Hence when adding more Tiers, these Tiers will have no price or size, thereby making it impossible to actually add Tiers.
All users can get 25% of their purchases by simply calling the registerReferralCode with another address, and then use that as a code as their referral. This is because the referral system is open to all users at all times. The gas cost to register another address as a referral is already offset by the cut from the discount on their purchases. 20% referral bonus and 5% discount would also help previous buyers just resell their older nfts at a cheaper price than the mint.
When the referrer is the buyer, they can get even more discounts on their purchases than they normally should. Users can game the system to pay less for more tokens this way.
In solidity, all addresses are uint160 values, ranging from uint160(0) to type(uint160).max, therefore all addresses can be reduced to base 10 numbers. In `generateReferralCode`, to get a ReferralCode for an address, a simple modulo is done. Modulo 1000000000. The issue with using such a simple method is that multiple addresses when computed will give the same referral code. for example: `0x0BA502c94dfDE079fC71f1234F4808a0d8396c02` and `0x602a41adc8968bBFfd3F97d310A9E6Da8e9e6c02` both have the same referral code of "2". The root cause of this is the value 1000000000 is relatively small in solidity, therefore it cannot provide uniqueness. This issue would actually prevent addresses from registering as referrals.
The mapping is declared but not implemented within the current codebase, as even the _isReferee() function which depends on it is redundant. If there is meant to be some sort of limit or check for referrers and referees within the contracts that affect their logic, it is not implemented here as it should.
The latest update to the contract removes the tokenURI override which enables the use of the tokenURI directly with the baseImageURI() that was created on this contract. SInce the full batch method from Thirdweb wasn’t used, but instead the setBaseImageURI() was created, the tokenURI() needs to be overridden.
The `withdraw()` and `withdrawToken()` function transfer the total ETH/Token balance to the admin address. This completely removes referrer rewards meaning referrers will not be able to withdraw their rewards if they have to do so using the `withdrawReferralReward()` function. This issue will cause permanent damage for all referrers with ETH rewards and temporary DOS for users with token rewards`
The smart contract currently exceeds the 24kb limit (introduced in the Spurious Dragon fork) and stands the risk of the code not being deployed to the blockchain without the optimizer enabled.
None of the functions emit events, both admin functions and user functions. Events are important for indexers monitoring contracts and providing offchain data.
| Address | Chain | Source code |
|---|---|---|
| 0x3a5d…7e750x3a5dff1badc2d23ec45ff19526ad7f0183557e75 | Chain: Arbitrum | Source code: Verified source |
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