OMNIA Protocol

CertiKRepository at commit b5098ad

CertiK reviewed Omnia Protocol on 11 Oct 2024: 17 findings, 0 critical; 13 marked fixed, 2 acknowledged, 2 open.

No open critical findings
Open the reportOpen Omnia Protocol’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings17

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

SeverityOpenAcknowledgedFixed
Critical000
High201
Medium000
Low0212
HighAdmin Can Withdraw All Staked FundsFixed

The `emergencyWithdraw()` function in the `Staking` contract permits the admin (via a time-lock contract) to extract any chosen amount of tokens from the contract, including those deposited by users. ```solidity=634 function emergencyWithdraw(uint256 amount, bytes32 reason) external whenPaused onlyAdmin { require(amount > 0, "Cannot be zero"); IERC20(_token).safeTransfer(msg.sender, amount); emit EmergencyTokenWithdraw(msg.sender, reason, amount); } ``` This function can only be activated when the staking contract is paused and enables the admin to remove tokens from the staking contract. There is a significant risk associated with this function, as it could lead to the loss of all staked funds by investors, even if the nodes they have staked in are operating correctly. This could occur if the function is misused.

Low`nonReentrant` Modifier can be Placed and ExecuteFixed

As a best practice, the `nonReentrant` modifier could be placed and executed before other modifiers in functions to prevent reentrancy attacks through other modifiers and make the code more efficient.

Low`stakeAmount` not updated in `unstakeTokens()`Fixed

The `unstakeTokens()` function permits users to retrieve their stakeAmount. The `_stakes[stakeId].amount` variable denotes the amount staked by the user in stakeId. Nonetheless, at the end of the function, the contract fails to update the variable following the transfer of stakeAmount to the user. ```solidity function unstakeTokens(uint256 stakeId) external nonReentrant { Stake storage stake = _stakes[stakeId]; ... _nodes[stakeNodeId].stakedAmount -= stakeAmount; IERC20(_token).safeTransfer(msg.sender, claimReward > 0 ? _sumUintInt(stakeAmount, claimReward) : stakeAmount); ... } ```

Low`TODO` CommentsFixed

The codebase contains several `TODO` comments indicating unfinished tasks or features that require further development or revision. The presence of these comments makes it challenging to ascertain the completeness and current status of the implicated sections.

LowConcerns about `stakeAmount` used when calculatinFixed

The `_claim()` function, being of private type, is tasked with determining the staking rewards a user is eligible for. This calculation initiates from the `stakeStartTimestamp`, incorporating both the first epoch's partial reward and the full epoch rewards up to the `latestClaimableEpoch`. For computing the reward of full epochs, the basis includes both the user's original `stakeAmount` and the first epoch's partial reward. Although the reward for the first epoch's partial period isn't actually claimed during its calculation, we aim to verify with the team whether the principal for calculating rewards for full epochs should indeed include the first epoch. Therefore, what we want to confirm with the team is the reason why the first epoch's partial reward is also used as the principal when calculating full epochs’ rewards.

LowIncompatibility With Deflationary Tokens (Non-staFixed

The project design may not be compatible with non-standard ERC20 tokens, such as deflationary tokens or rebase tokens. The functions use `transferFrom()` to move funds from the sender to the recipient but fail to verify if the received token amount matches the transferred amount. This could pose an issue with fee-on-transfer tokens, where the post-transfer balance might be less than anticipated, leading to balance inconsistencies. There might be subsequent checks for a second transfer, but an attacker might exploit leftover funds (such as those accidentally sent by another user) to gain unjustified credit.

LowInconsistency Between Comment and CodeFixed

There are discrepancies between the pointed comment and code implementation in the runtime configuration: In the `Staking` contract, there are the following inconsistencies between code and comments: - In the `_getDaysUntilEpochEnd()` function, the code first calculates the timestamp and then divides by `1 day`, whereas the comment suggests dividing by `1 day` first. - In the `_getPeriodCompoundInterestRate()` function, the comment indicates that positive rates are derived by subtracting 1 from the calculation result, and negative rates are obtained by subtracting the calculation result from 1. However, the code does not involve any operations related to 1.

LowIncorrect for loop initial valueFixed

This loop iterates from the last epoch a stake was claimed for a given `stakeId` up to the `latestClaimableEpoch`, triggering an `EpochClaimed` event for each. However, if `stakeLastClaimedEpoch` is 0, the epoch of the event should begin at `stakeFirstEpoch` for that specific `stakeId`, rather than starting from 1.

LowLack of Storage Gap in Upgradeable ContractFixed

When updating upgradeable smart contracts for new features or bug fixes, it's essential to keep the state variables' declaration order unchanged to avoid storage layout issues. A practical solution is to include unused state variables or explicitly named storage gaps (like `__gap`) in the base contracts. This foresight allows reserved slots for future use, ensuring that any additions to the contract's state won't disrupt the storage pattern of derived contracts or the compatibility with previously deployed versions. The problem of "Lack of Storage Gap in Upgradeable Contract" occurs when these storage gaps are not incorporated into the base contract's logic. As a result, if new state variables are added to the base contract, they might overwrite existing variables in the child contracts due to storage slot collisions. For detailed guidelines and best practices, refer to the following OpenZeppelin documentation: - https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps

LowMissing Emit EventsFixed

There should always be events emitted in the sensitive functions that are controlled by centralization roles.

Show 3 more
LowMissing Zero Address ValidationFixed

Addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. ```solidity=274 function stakeTokensFor( address staker, bytes32 nodeId, uint256 amount, uint16 stakingDays ) external whenNotPaused nonReentrant returns (uint256) { ```

LowUnexpected Results of `_getNextDayStartTimestamp(Fixed

The `_getNextDayStartTimestamp()` function is designed to determine the start timestamp for the next day. However, when the time difference between the provided `timestamp` and the `contractStartTimestamp` equals exactly `1 days`, the outcome of the calculation `((1 days - ((timestamp - contractStartTimestamp) % 1 days)) % 1 days)` turns out to be zero. Consequently, this leads to the function returning the original input timestamp, instead of advancing to the next day's start timestamp as intended. ```solidity=821 function _getNextDayStartTimestamp(uint32 timestamp, uint32 contractStartTimestamp) private pure returns (uint32) { /// @dev timestamp + secondsTillNextDayStart return timestamp + ((1 days - ((timestamp - contractStartTimestamp) % 1 days)) % 1 days); } ``` Within the `_stakeTokens()` function, this particular calculation outcome results in the stake's start time being logged as the present timestamp. This deviates from the intended functionality, which is to set the stake's start time at the beginning of the following day. ```solidity function _stakeTokens( address staker, bytes32 nodeId, uint256 amount, uint16 stakingDays ) private returns (uint256) { ... uint32 startTimestamp = _getNextDayStartTimestamp( _toUint32(block.timestamp), _toUint32(_contractStartTimestamp) ); ... _stakes[stakeIdCounter] = Stake(nodeId, amount, 0, startTimestamp, 0, staker, stakingDays, apyBoost); ... } ```

LowUnprotected InitializerFixed

The `Staking` logic contract do not protect the initializer. An attacker can call the `initialize` function and assume ownership of the logic contract, whereby she can perform privileged operations that trick unsuspecting users into believing that she is the owner of the upgradeable contract. ```solidity=15 contract Staking is Initializable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable, PausableUpgradeable { ``` - `Staking` is an upgradeable contract that does not protect its initializer. ```solidity=402 function initialize(address stakingToken) public initializer { ``` - `initialize` is an unprotected initializer function.

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 (6)

  • https://github.com/omniaprotocol/staking-contract/src/TimelockAdmin.sol at commit b5098ad7858a831e92422a5a37706c55c1b68ce3
  • https://github.com/omniaprotocol/staking-contract/src/Staking.sol at commit c2b47c814b23196fa17f3d5ea7c3b016e0d5ee52
  • https://github.com/omniaprotocol/staking-contract/src/Staking.sol at commit c2ae58e0a056e6153905d05b16070ae6f3a84caa
  • https://github.com/omniaprotocol/staking-contract/src/TimelockAdmin.sol at commit 765397db8afe9306cab533902757aee5f4560f24
  • https://github.com/omniaprotocol/staking-contract/src/Staking.sol at commit b5098ad7858a831e92422a5a37706c55c1b68ce3
  • https://github.com/omniaprotocol/staking-contract/src/Staking.sol at commit 765397db8afe9306cab533902757aee5f4560f24

Part of Omnia Protocol’s security record

Omnia Protocol
Audits1 audit by 1 firmLast audit Oct 2024, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open Omnia Protocol’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