CertiK reviewed Ommniverse on 5 Mar 2023: 27 findings, 0 critical; 25 marked fixed, 2 open.
The original report was removed from its address (seen 19 Oct 2026).
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 4 |
| Medium | 2 | 0 | 8 |
| Low | 0 | 0 | 13 |
In the contract **Governance**, the role **Owner** has authority over the following functions: - function createProposal(), to create a proposal. - function executeProposal(), to execute a proposal. Any compromise to the **Owner** account may allow a hacker to take advantage of this authority. In the contract **Fractions**, the role **Owner** has authority over the following functions: - function addMinter(), to add the address `minter` as the `Minter` role. - function removeMinter(), to remove the address `minter` from the `Minter` role. - function updateBaseUri(), to update the base URI of the fractions. Any compromise to the **Owner** account may allow a hacker to take advantage of this authority. In the contract **Fractions**, the role **Minter** has authority over the following functions: - function mint(), to mint any amount of fractions to any address. Any compromise to the **Minter** account may allow a hacker to take advantage of this authority. In the contract **NftVaultFactory**, the role **Owner** has authority over the following functions: - function setFeeReceiver(), to update the fee receiver. - function setFee(), to update the fee. Any compromise to the **Owner** account may allow a hacker to take advantage of this authority. In the contract **Ommniverse**, the role **Owner** has authority over the following functions: - function pause(), to return the contract to a normal state. - function unpause(), to trigger a stopped state. - function mint(), to mint `OMMI` ERC20 tokens to any address. Any compromise to the **Owner** account may allow a hacker to take advantage of this authority. In the contract **VaultFactory**, the role **Owner** has authority over the following functions: - function setFeeReceiver(), set update the fee receiver. - function setFee(), to update the fee. - setAuctionLength(), to update the auction length. Any compromise to the **Owner** account may allow a hacker to take advantage of this authority.
`Fractions`,`NftVault`, `NftVaultFactory`,`OmmniverseCollection`,`Ommniverse`,`Vault`, and `VaultFactory` are upgradeable contracts, the owner can upgrade the contract without the community's commitment. If an attacker compromises the account, he can change the implementation of the contract and drain tokens from the contract.
All calls of the form `transferFrom(from, dest, amount)` where the `dest` address is zero will fail. ```solidity OmmniToken.transferFrom(msg.sender, address(0), burn) ```
The `vote()` function allows token holders to vote on proposals, and the number of votes cast depends on the user's total quantity of voting tokens. Although each user can only vote once, the voting tokens are not locked, allowing the holder of the voting tokens freely transfer the voting tokens to any address after casting the vote, giving that address more voting power and potentially manipulating the voting outcome. ```solidity=108 function vote(uint256 _proposalId, bool _vote) public { require(proposals[_proposalId].votingPeriod > block.timestamp); require(!voters[_proposalId][msg.sender], "Already voted"); if (_vote) { voteTotals[_proposalId].yes += token.balanceOf(msg.sender); } else { voteTotals[_proposalId].no += token.balanceOf(msg.sender); } voters[_proposalId][msg.sender] = true; emit VoteCast(_proposalId, msg.sender, _vote); } ```
In the function `end`, the fee sent to the fee receiver is not correctly calculated, there is a duplicated `liveprice` multiplied in the calculation formula. ```solidity _sendOmmi( ISettings(settings).feeReceiver(), livePrice * livePrice * ((ISettings(settings).fee()) / 1000) ); ```
The variable `address(this).balance` is being used to calculate the share of the `Ommni` token is incorrect. `address(this).balance` represents the balance of ETH in the contract `Vault`, it should use the balance of the `Ommni` token. ```solidity=204 fractionValue = address(this).balance / IFERC1155(fractions).totalSupply(fractionsID); ``` ```solidity=215 uint256 share = bal * fractionValue; IFERC1155(fractions).burn(msg.sender, fractionsID, bal); _sendOmmni(msg.sender, share); ```
The parameter `address(this)` passed in function `IFERC1155(fractions).mint()` is incorrect, the parameter should be the address of the vault. ```solidity=89 uint256 fractionId = IFERC1155(fractions).mint(address(this), _amount); ``` ```solidity=46 function mint( address vault, uint256 amount ) external onlyMinter returns (uint256) { count++; idToVault[count] = vault; _mint(msg.sender, count, amount, "0"); _totalSupply[count] = amount; return count; } ```
As per the code comments of the `start()` function, `reservePrice` in `OMMI` must be sent to the contract from the caller. Hence there should be a validation of the amount to ensure that the starting auction price is not smaller than the reserve price.
In the `buyFraction()` function, there should be a validation to ensure that users can only buy NFT fractions before the NFT auction starts.
There is no validation that an existing vesting schedule was created for the beneficiary, so the previous vesting schedule of the beneficiary will be overwritten. ```solidity=37 mapping(address => VestingSchedule) vestingSchedules; ``` ```solidity=88 vestingSchedules[_beneficiary] = VestingSchedule( _beneficiary, _start, cliff, _duration, _slicePeriodSeconds, _amount, 0 ); ```
The functions that are declared `payable` can receive ether. As per the description in the code comments, the functions `buyFraction()`, `start()`, and `bid()` are not designed for receiving ether, so they are redundant to have the `payable` modifier. ```solidity=108 /// @notice buys fractions of the Nft. Must send price in Ommni function buyFraction(uint256 _amount) external payable nonReentrant { ``` ```solidity=154 /// @notice kick off an auction. Must send reservePrice in Ommni function start(uint256 _amount) external payable nonReentrant { ```
In the function `start()`, there is no validation to ensure that the auction does not start before fractions of the NFT are sold out.
In the function `_computeReleasableAmount`, the release amount is calculated from the cliff time instead of the start time. We would like to confirm with the client if the current implementation aligns with the original project design.
The variables do not have reasonable boundaries, so they can be given arbitrary values after deploying.
Functions that affect the status of sensitive variables should be able to emit events as notifications to customers.
Addresses should be checked before assignment or external call to make sure they are not zero addresses. ```solidity=84 fractions = _fractions; ``` - `_fractions` is not zero-checked before being used. --- ```solidity=86 underlying = _underlying; ``` - `_underlying` is not zero-checked before being used. --- ```solidity=88 curator = _curator; ``` - `_curator` is not zero-checked before being used. --- ```solidity=34 fnft = _fnft; ``` - `_fnft` is not zero-checked before being used. --- ```solidity=35 ommni = _ommni; ``` - `_ommni` is not zero-checked before being used. --- ```solidity=69 feeReceiver = _feeReceiver; ``` - `_feeReceiver` is not zero-checked before being used.
The cited address input is missing a check that it is not `address(0)`.
Users can buy ERC721 tokens in the `NftVault` contract using `OMMI` tokens, which are sent to the fee receiver. However, no `OMMI` tokens are sent to the ERC721 token's owner. Similarly, In the `Vault` contract, if the ERC721 token is successfully auctioned off, all `OMMI` tokens will be possibly distributed by the fractions owners, and there is no withdrawal function for the vault owner to retrieve the `OMMI` tokens in the contract. We would like to confirm with the client that the current implementation aligns with the original project design.
Because there is no validation to guarantee the name and symbol are unique, anybody may use the function `mintContact()` to establish the `UserCllection` contract with the same name and symbol.
The aforementioned lines cast the contract to the payable address yet no ether transfer is performed. ```solidity=154 address payable beneficiaryPayable = payable( vestingSchedule.beneficiary ); ```
Solidity version >=0.8.0 includes checked arithmetic operations and underflow/overflow by default, making SafeMath redundant.
When the supply of `OMMI` tokens reaches half of the total supply, half of the `OMMI` tokens paid for buying NFT/ERC1155 will be burned. We failed to find any design documentation for this part of the business logic. ```solidity=84 if (OmmniToken.totalSupply() <= 3000000 * 10 * 18) { uint256 burn = nftPrice / 2; require( OmmniToken.transferFrom( msg.sender, ISettings(settings).feeReceiver(), burn ) && OmmniToken.transferFrom(msg.sender, address(0), burn), "Ommni Transfer failed" ); } ``` ```solidity=125 if (OmmniToken.totalSupply() <= 3000000 * 10 * 18) { uint256 burn = (_amount * omni_price) / 2; require( OmmniToken.transferFrom(msg.sender, address(this), burn) && OmmniToken.transferFrom(msg.sender, address(0), burn), "Ommni Transfer failed" ); } ``` We would like the team to elaborate more about the purpose of burning tokens and the intention of the condition: ```solidity OmmniToken.totalSupply() <= 3000000 * 10 * 18 ```
The return value of the transfer()/transferFrom() call is not checked. ```solidity=141 OmmniToken.transferFrom(msg.sender, address(this), _amount); ``` --- ```solidity=158 OmmniToken.transferFrom(msg.sender, address(this), _amount); ``` --- ```solidity=197 OmmniToken.transfer(_to, _value); ```
The payable receive() and fallback() functions allow ethers to be transferred to the contract by mistake, and there is no way to get ethers out of the contract, so those ethers are locked in the contract and lost forever.
One or more logic contracts do not protect their initializers. An attacker can call the initializer 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=9 contract Fractions is Initializable, ERC1155Upgradeable, OwnableUpgradeable { ``` - `Fractions` is an upgradeable contract that does not protect its initializer. ```solidity=20 function initialize() public initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=15 contract NftVault is ``` - `NftVault` is an upgradeable contract that does not protect its initializer. ```solidity=44 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=11 contract NftVaultFactory is Initializable, OwnableUpgradeable { ``` - `NftVaultFactory` is an upgradeable contract that does not protect its initializer. ```solidity=38 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=11 contract OmmniverseCollection is ``` - `OmmniverseCollection` is an upgradeable contract that does not protect its initializer. ```solidity=24 function initialize() public initializer { ``` - `initialize` is an unprotected initializer function. --- ```solidity=16 contract Vault is ``` - `Vault` is an upgradeable contract that does not protect its initializer. ```solidity=71 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=11 contract VaultFactory is ``` - `VaultFactory` is an upgradeable contract that does not protect its initializer. ```solidity=49 function initialize( ``` - `initialize` is an unprotected initializer function.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (14)
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