As of 3 Nov 2026, Omnia Protocol has 1 audit report on Trustblock, by CertiK, dated 11 Oct 2024. 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 |
|---|---|---|---|---|---|
| OMNIA Protocol | Security firm: CertiK | Date: | Scope: Repository at commit b5098ad | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 2 | 0 | 1 |
| Medium | 0 | 0 | 0 |
| Low | 0 | 2 | 12 |
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.
Open the reportAs 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.
Open the reportThe `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); ... } ```
Open the reportThe 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.
Open the reportThe `_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.
Open the reportThe 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.
Open the reportThere 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.
Open the reportThis 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.
Open the reportWhen 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
Open the reportThere should always be events emitted in the sensitive functions that are controlled by centralization roles.
Open the reportAddresses 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) { ```
Open the reportThe `_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); ... } ```
Open the reportThe `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.
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="omnia-protocol"></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