CertiK reviewed Equeus (Equeus - Audit) on 20 Oct 2023: 34 findings, 2 critical; 25 marked fixed, 1 acknowledged, 8 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its 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.
If 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.
In 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.
One 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`.
When `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.
A 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.
In 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.
Throughout 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)).
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (43)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’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