Equeus - Audit

CertiKFiles named in the report

CertiK reviewed Equeus (Equeus - Audit) on 20 Oct 2023: 34 findings, 2 critical; 25 marked fixed, 1 acknowledged, 8 open.

2 critical findings, fixed
Open the reportOpen Equeus’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings34

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical002
High010
Medium003
Low8020
Low`fullfillRandomWords()` May RevertOpen

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.

Low`mysteryPackValue` May Always Equal `amount`Open

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.

LowCan Store Value To Avoid Repeated CalculationsOpen

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.

LowInefficient Memory ParameterOpen

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`.

LowLottery VRF RequestsOpen

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.

LowPotential ReentrancyOpen

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.

LowRedundant ChecksOpen

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.

LowUse of `abi.encodePacked()`Open

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)).

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (43)

  • contracts/base/WithUSD.sol
  • contracts/base/WithEqueusCheckoutGuard.sol
  • contracts/EqueusCareerPlanPool/EqueusCareerPlanPool.sol
  • contracts/EqueusLotteryPools/EqueusLotteryPools.sol
  • contracts/EqueusMeritocraticPool/IEqueusMeritocraticPool.sol
  • contracts/EqueusNFTsVault/IEqueusNFTsVault.sol
  • contracts/EqueusCheckout/EqueusCheckout.sol
  • contracts/EqueusUserRegistry/IEqueusUserRegistry.sol
  • contracts/base/WithEqueusFairDraw.sol
  • contracts/EqueusMeritocraticPool/EqueusMeritocraticPool.sol
  • contracts/RunyStaking/RunyStaking.sol
  • contracts/base/WithAuthOperators.sol
  • contracts/base/WithEqueusCareerPlanPool.sol
  • contracts/base/WithOperators.sol
  • contracts/RunyStaking/IRunyStaking.sol
  • contracts/EqueusFairDraw/IEqueusFairDraw.sol
  • contracts/base/WithEqueusFairDrawGuard.sol
  • contracts/base/WithRunyPriceFeed.sol
  • contracts/EqueusPioneersPool/IEqueusPioneersPool.sol
  • contracts/EqueusNFTsVault/EqueusNFTsVault.sol
  • contracts/base/WithEqueusMysteryPacks.sol
  • contracts/EqueusMysterypacks/EqueusMysteryPacks.sol
  • contracts/base/WithRuny.sol
  • contracts/base/WithCheckoutOperators.sol
  • contracts/EqueusCareerPlanPool/IEqueusCareerPlanPool.sol
  • contracts/EqueusUserRegistry/EqueusUserRegistry.sol
  • contracts/base/WithEqueusPioneersPool.sol
  • contracts/base/WithTokensReceiver.sol
  • contracts/EqueusCheckout/IEqueusCheckout.sol
  • contracts/base/WithRunyStaking.sol
  • contracts/Runy/IRuny.sol
  • contracts/EqueusMysterypacks/IEqueusMysterypacks.sol
  • contracts/base/WithEqueusNFTsVault.sol
  • contracts/base/WithRunyStakingGuard.sol
  • contracts/Runy/Runy.sol
  • contracts/base/WithEqueusCheckout.sol
  • contracts/base/WithEqueusLotteryPools.sol
  • contracts/EqueusLotteryPools/IEqueusLotteryPools.sol
  • contracts/base/WithEqueusUserRegistry.sol
  • contracts/EqueusPioneersPool/EqueusPioneersPool.sol
  • contracts/EqueusFairDraw/EqueusFairDraw.sol
  • contracts/EqueusDateLibrary.sol
  • contracts/base/WithEqueusMeritocraticPool.sol

Part of Equeus’s security record

Equeus
Audits1 audit by 1 firmLast audit Oct 2023, by CertiK.
Critical findingsNo open critical findingsIn that report, 2 critical findings were reported. Each is marked fixed by the firm that reported it.
Open Equeus’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

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