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 VRF service will not make another call if `fullfillRandomWords()` reverts. However, there are multiple ways this callback may revert. Losing the random numbers generated.
Open the reportIf only the `EqueusCheckout` contract will be given the `CheckoutOperator` role, then the inputs `mysteryPackValue` and `amount` in the function `addEntry` of `EqueusMeritocraticPool` will always be the same.
Open the reportIn the contract `RunyStaking`: - the function `getReleaseAmounts` calculates the release amounts each time as `amount/4` or for the final release amount the total amount minus the three previous values. As the amounts for each will be the same it can be stored in a temorary variable so that the first three `releaseAmounts` will simply be equal to the temporary variable and the final `releaseAmounts` will be the amount minus three times the variable. This saves 321 gas on each call to `stake()`. --- Similarly, in the contract `EqueusCheckout`: - the amounts calculated for commissions are calculated twice, as opposed to storing the calculation in a temporary variable.
Open the reportOne or more parameters with `memory` data location are never modified in their functions and those functions are never called internally within the contract. Thus, their data location can be changed to `calldata` to avoid the gas consumption copying from calldata to memory. This will increase the gas cost on deployment, but will save gas on each function call. This is consistent throughout the codebase for inputs that are `strings`.
Open the reportWhen `closeLotteryPool()` is called the `VRFRequestStatus` struct is used to for the request with the `mysteryPackId`, `username`, `sponsor`, `addr`, `nfts`, and `mysteryPackName` all set to default values as they are not used to process the lottery. In addition, it requests the `numWords` amount `randomWords`, which is originally set to 2, when it only uses 1 random word.
Open the reportA reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. The finding is a severity of informational because, in the worst-case scenario, out-of-order events can occur. If the intended token for `usd` is `USDC`, then it is an upgradeable contract. As such the implementation can be upgraded to include hooks that can allow possible reentrancy during transfers. The `runy` token is set with the `setRuny()` function and as such can be changed to a different contract. If this new contract includes hooks, then it can allow possible reentrancy during transfers. For ERC721 tokens, function `safeTransferFrom()` makes an external call via `onERC721Received()` that can be used for reentrancy. Chainlink's VRF coordinator may become compromised and the external call to `requestRandomWords()` may be used for reentrancy.
Open the reportIn the contract `EqueusUserRegistry`: - the check in the function `registerUser()` that `usernameExists(_sponsor)` already checks that `usernameToUser[_sponsor].createdAt` is nonzero. Thus checking again that ` usernameToUser[_sponsor].createdAt != 0` is unnecessary. --- In the contract `EqueusCheckout`: - the check in the function `claimFastBonus()` that the `username` is not the empty string is redundant if the suggested checks are added when importing users. This is because it is already checked that `user.createdAt > 0` so that the username exists. And provided the empty string can never be a username this ensures that the `user.username` is not the empty string; - similarly, the check in the function `claimFastBonus()` that the `mysteryPackName` is not the empty string is redundant. This is because when the bonus count is 5 or more, then it assigns the `mysteryPackName` and the `username`. Thus if the bonus has not been achieved yet, then both of these will be the empty string. Thus in this case it will be covered by the other checks similar to above. If the bonus has been achieved, then these will be assigned and not be the empty string provided the checks for the u`sername` and `mysteryPackName` suggested in the finding *Missing Checks* are added. --- In the contract `EqueusLotteryPools`: - the check in the function `closePool()` does not need to include a check for `hasProcessedReward` is false and that `totalEntries` are nonzero. This is because it is first checked that the pool is initialized and not closed. The only way for a pool to be initialized is through the function `addEntry()` which can only be called by the `FairDrawOperator`. Provided the `FairDrawOperator` is only set to be the `EqueusFairDraw` contract, then it will only be called with a nonzero amount so that when a pool is initialized it will cause the `totalEntries` to be nonzero. For the other, `hasProcessedReward` is only set to true by the function `airdropPrize()` which can only be called if the pool is closed. Thus if the pool is not closed, necessarily `hasProcessedReward` is false. --- In the contract `RunyStaking`: - the functions `unstake()` and `claimAPY()` check that the `user.username` is not the empty string and that `user.createdAt` is nonzero. Provided the suggest checks are added to `importUser()` (in particular that the input username is not the empty string) the check that `user.createdAt` is nonzero will suffice. This is because when a user is imported or registered, they must have a non-empty username and `createdAt` will always be set to the `block.timestamp` when they were imported or registered.
Open the reportThroughout the codebase, to check if two strings are the same, they are converted to bytes through `bytes()` and then hashed via `keccak256()` to compare. However, in `EqueusUserRegistry`, `EqueusMeritocraticPool`, and `EqueusCheckout` there are instances where they are converted to bytes with `abi.encodePacked` instead, which costs more gas. In addition, `abi.encodePacked` may be removed in future solidity versions (see this issue: [https://github.com/ethereum/solidity/issues/11593](https://github.com/ethereum/solidity/issues/11593)).
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