As of 3 Nov 2026, Equeus has 1 audit report on Trustblock, by CertiK, dated 20 Oct 2023. That report lists no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Equeus - Audit | Security firm: CertiK | Date: | Scope: Files named in the report | 2 critical findings, fixed | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 2 |
| High | 0 | 1 | 0 |
| Medium | 0 | 0 | 3 |
| Low | 8 | 0 | 20 |
The only way for USD to be transferred from the `EqueusCareerPlanPool` is through the function `airdropPlan()`. However, this function transfers the USD using `transferFrom()` and has not given itself any approval. This will cause the `airdropPlan()` function to revert making it impossible to withdraw the USD sent to the contract.
Open the reportIn the function `stake()` the calculated `totalReward` is calculated with the APY for 1 year and is the maximum amount that can be claimed. If a user leaves a portion of their APY unclaimed and then attempts to claim it after a year, they will be unable to do so. The same logical issues also appear in `calculateNextClaimableAPY()`.
Open the reportWhen `Runy` is staked for a user the amount to be staked is minted and added to the total supply. If enough tokens are staked the `totalSupply` can reach the `MAX_SUPPLY` so that no more tokens can be minted. In the function `claimAPY`, the amount to claim must be minted causing users to be unable to claim their APY as the minted amount would exceed the `MAX_SUPPLY`.
Open the reportThe function `withdrawNfts()` allows the owner to withdraw any NFT from the contract, including skins and tracks. If a skin or track is withdrawn on accident, then the `skins[]` and `tracks[]` array will not be updated which can cause the function `sendNFT()` to revert unexpectedly when it attempts to transfer an NFT that the contract does not own.
Open the reportThe function `addEntry()` checks that it is the first line and that `hasPurchasedFirstLine[username][purchaser]` is false. If these checks pass, then `hasPurchasedFirstLine[username][purchaser]` is set to true, however, `firstLineQualifiedEntries[username]` is only incremented if the `mysteryPackValue` is greater than or equal to the `MYSTERY_PACK_VALUE_REQUIRED`. If the purchaser purchases a mystery pack below this value, then they will not be able to be considered again for the users `firstLineQualifiedEntries` even if they buy a mystery pack in the same month that is greater than or equal to the `MYSTERY_PACK_VALUE_REQUIRED`. Thus a purchaser can be excluded from contributing to a user's `firstLineQualifiedEntries` even though they have not previously contributed to it.
Open the reportThe contract `EqueusCheckout` inherits `WithTokensReceiver`, so it can withdraw any `ERC20` token held by the contract, including USD. As 10% of a user's commission is kept to be able to buy `mysteryPacks` with credits, this amount must always be held by the contract or `purchaseWithCredits()` will revert when it processes commissions.
Open the reportWe would like to clarify what the design intent is for NFTs. In the current implementation each mystery pack is assigned a number of NFTs which are then distributed between skins and tracks based on random numbers and probabilities. This means that the amount of skin and track NFTs will not be known until after they are distributed. In addition, if there is only one NFT left for a mystery pack, then that NFT will only be able to be a track, and cannot be a skin. This ensures that there will always be at least one track NFT distributed for a mystery pack. However, the `EqueusNFTsVault` holds a fixed amount of skins and tracks. Thus it is possible that it does not contain anymore of one type of NFT and the `EqueusFairDraw` contract may have a user win that type of NFT. This will cause the function `processMysteryPackOpening()` to revert when it attempts to `sendNFT()`. This requires that new NFTs are given to the NFT vault before such a situation arises. We would like to know if this is the design intent and if so how it will be ensured that enough NFTs are held by the vault to guarantee that `processMysteryPackOpening()` does not revert as in the scenario above.
Open the reportIf `removeUserFromPlan()` is called on a username that has been registered, but has not been added to the input `plan`, then this function will run successfully. This can cause confusion as the only way to tell that the user was not removed from the plan, as they were not in it in the first place, is to see that no `CareerPlanExit` event was emitted.
Open the reportBoth the functions `withdrawSkins()` and `withdrawTracks()` transfer all the respective NFTs held in their respective arrays. If the `tracks[]` or `skins[]` array is too large, this can cause these functions to exceed the block gas limit.
Open the reportIf a user buys a high value mystery pack, for example a "Rhodium" pack, then they will receive a large number of `activeLines` that are active for 30 days. However, if the same user then purchases a lower cost mystery pack, for example an "Entry" pack this gives a smaller number of `activeLines` before this 30 days, and it will overwrite the users active lines so that they will have less active lines.
Open the reportThe interface `IEqueusMysteryPacks` has a file name of `IEqueusMysterypacks.sol` which differs due to a missing capital P.
Open the reportIn the contract `RunyStaking`, the function `claimAPY()` calculates the amount of APY to be claimed. However, in the calculation it divides before multiplying when calculating the `rewardForTimePassed`, resulting in a rounding error as the `reward` will be rounded down. As the current implementation at maximum allows a user to claim APY after 365 days, this rounding error will result in a user being unable to claim their full APY.
Open the reportBoth `msg.sender` and `_msgSender()` are used throughout the codebase. If `_msgSender()` is ever updated to anything other that `msg.sender`, this may cause inconsistencies.
Open the reportThe function `numWords()` does not make any checks on the input value. However, if the values is set to 1 or 0 it can cause the following potential issues. If the value is set to 1, then the lottery will work as normal, but `processMysteryPackOpening()` will always have a user win a track. This is because `_randomWords[1]` will default to 0 so that is always less than the track probabilities. Similarly, if the value is set to 0 then then both random numbers used will be known to be 0.
Open the reportThe whitepaper states: When there are more than 100 networkers who meet all the requirements, only the 100 accounts ranked according to this priority criterion are selected: - Those who have a higher numbers of active direct referrals. - In the event of a tie on the number of first active lines, the turnover of the first line is counted. - In case of a tie on the turnover of the first line, the turnover of the second line is counted. However, this does not match the implementation. If the pool is full and a new user meets the requirements, then it is checked if the current lowest user's total amount in the pool is greater than the new user's total amount. If not, then the current lowest user in the pool is removed and the new user is added to the pool. In particular, if they have the same total amount, the lowest user in the pool is still removed and the new user added.
Open the reportIn the contract `EqueusCareerPlanPool`: - the function `airdropPlan()` does not check that the input `amount` to be airdropped is less than or equal to the `balance`. If an amount greater than `balance` is used, then the function will revert late into the function call costing more gas; - the function `airdropPlan()` does not check that the `fromIndex` is less than the `toIndex`. If the `toIndex` is less than or equal to the `fromIndex`, then the transaction will succeed without executing any of the for loops logic. --- In the contract `EqueusUserRegistry`: - the function `importUser()` is only callable by the `owner`, however, it does not check that the input `_user` is a valid username or already exists. The `owner` may accidentally successfully call this function with an invalid username creating a `User` with a username that is not intended to be accepted. Similarly, the `owner` may accidentally successfully call this function with a username that already exists and overwrite the current `User`; - the function `importUser()` does not check that the input `_sponsor` is a different user than the input `_user`. If a `_sponsor` is accidentally set as the `_user`, then they will be all their own lines giving them an unfair percentage of commissions. - the function `setSocialMarketingSystemEnabled()` should check that the username exists. --- In the contract `EqueusCheckout`: - the function `purchaseWithCredits()` allows a user to purchase a mystery pack with accumulated credits, however, it does not check `isCheckoutOpen`. Similarly, `claimFastBonus()` allows a user to claim a mystery pack, but does not check `isCheckoutOpen`. --- In the contract `EqueusMysteryPacks`: - the function `updateMysteryPacks()` does not check that the input `names` are not the empty string. --- In the contract `EqueusFairDraw()`: - the function `closeLotteryPool()` does not check that it is passed the month for the lottery. This can cause potential issues if it is called in the middle of a month, when users should still be able to receive tickets for that months lottery; --- In the contract `EqueusLotteryPools()`: - the functions `setPoolMonths()` and `setPoolYears()` should ensure that the `poolMonths[]` and `poolYears[]` array have the same length. This is because if they do not and the `nextRewardPoolIndex` exceeds the index of the smaller array, then it will default to 0. --- In the contract `EqueusMeritocraticPool()`: - the function `airdropRewards()` does not check that the input month and year. If this is not checked then it is possible to airdrop rewards for the wrong month and year causing the rewards to be airdropped with the wrong month and year emitted.
Open the reportIn the contract `EqueusCareerPlanPool`: - in the function `addUserToPlan()`, if the user is already in the plan, then the plan expiration is updated, however no event is emitted for this; - in the function `emptyPlan()`, the function deletes the input `plan` users without emitting an event. --- In the contract `EqueusFairDraw`: - the functions `openMysteryPack()` and `closeLotteryPool()` both emit the `RequestSent` event. However, there is no distinction between which function requested the randomness. We recommend making separate events to distinguish between requests that are sent for opening mystery packs versus closing lotteries. --- In the contract `RunyStaking`: - the `Staked` event does not include the APY. As the APY varies between packs, we recommend also emitting the APY for the stake. --- In the contract `EqueusUserRegistry`: - the `UserRegistered` event does not emit all relevant information if `importUser()` is called. This is because `importUser()` also has the inputs `_addr`, `_activeLines`, and `_activeLinesExpireTime`.
Open the reportCurrently as long as a username has only the accepted characters it is valid regardless of its length. This allows a user to make a username with a large amount of characters. As the `operator` or `owner` registers or imports the users allowing large usernames will result in higher gas costs.
Open the reportThe function `setUserSponsor()` allows the possibility of sponsor loops. For example `UserA` may have a sponsor `UserB`, and `UserB` has the sponsor `UserC`. This function can then change the sponsor of `UserB` to `UserA`. This will then mean that if a line is `UserA` or `UserB` all lines after will be `UserA` and `UserB`. Similarly this can be user to have a loop with more than 2 users. This allows the users to obtain more commissions than intended
Open the reportIn the contract `EqueusCheckout`, the functions `purchaseMysteryPack()` and `purchaseWithCredits()` are payable, however, they do not contain logic to handle the native token. If native token is included as the `msg.value` then the function call will succeed and the native token will be locked in the contract.
Open the reportIn the function `processUserCommission()`, 80% is given in USD to the sponsors address, 10% is allocated to credit, and the last 10% is given out in runy. These three calculations all round down, so that in total they may not account for the full commission.
Open the reportIn the contract `EqueusChecout` the following typos/inconsistencies were found: - the comment at the start of the contract says "Interface", when it should be "Contract". --- In the contract `RunyStaking` the following typos/inconsistencies were found: - in the functions `unstake()`, `claimAPY()`, and the error message "staking_exausted" should be "staking_exhausted". --- In the contract `EqueusUserRegistry` the following typos/inconsistencies were found: - the comment above the function `registerUser()` says it is only callable by "auth operators", however, it is only callable by "operators". - the comment above the function `setUserAddress()` says that it is only callable by "auth operators", however, it is callable by a variety of entities not just `authOperator`. - the comment above the function `getUserByAddress()` does not match the function. --- In the contract `EqueusCareerPlanPool` the following typos/inconsistencies were found: - the comment at the start of the contract is for the Equeus Pioneers Pool and should instead be for the Equeus Career Plan Pool. --- In the contract `IEqueusCareerPlanPool` the following typos/inconsistencies were found: - the comment at the start of the contract is for the Equeus Pioneers Pool Interface and should instead be for the Equeus Career Plan Pool Interface. --- In the contract `EqueusNFTsVault` the following typos/inconsistencies were found: - the comment at the start of the contract is for Equeus Mystery Packs and should instead be for Equeus NFT Vault.
Open the reportThe return value of the transfer()/transferFrom() call is not checked. ```solidity=18 IERC20(token).transfer(to, amount); ```
Open the report`MysteryPack` has the parameters `stakingPhasesPercentage`, `stakingPhasesDuration`, and `activeLinesExpireIn`, however, it seems these parameters are never used.
Open the reportThe function `equeusPioneersPool.addUsernameToPool()` returns a bool to indicate if a user was added to the pool or not.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="equeus"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something 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