As of 3 Nov 2026, NvirWorld has 1 audit report on Trustblock, by CertiK, dated 30 Jan 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 |
|---|---|---|---|---|---|
| NvirWorld - Audit | Security firm: CertiK | Date: | Scope: Files named in the report | 2 critical findings, fixed | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 2 |
| High | 0 | 2 | 0 |
| Medium | 0 | 0 | 3 |
| Low | 2 | 0 | 7 |
Users have the option to acquire tokens from the `LaunchPad` via the `participateInSale` function. The tokens purchased by users are not immediately transferred to their accounts; instead, they undergo a period of time before being released. The issue arises from the fact that the code doesn't validate whether the `saleToken` balance of the contract is sufficient for sale when users make a purchase. Consequently, users can proceed with the purchase and make payments even when the `saleToken` balance are insufficient. However, when users attempt to release their tokens later, there might not be adequate `saleToken` in the contract.
Open the reportThe `releaseVestedTokens` function is intended to allow the user to claim the vested tokens after the vesting starts. However, a critical flaw exists as the vested tokens are transferred to `msg.sender` instead of the token owner, resulting in the loss of tokens for the rightful owner. ```solidity=368 saleToken.transfer(msg.sender, _vestingAmount); ``` This vulnerability enables the malicious user to call the function, triggering the release of vested tokens belonging to a user and transferring them to the malicious user's address, allowing unauthorized seizure of all vested tokens.
Open the reportWhen a user accesses the contract to make a purchase, the contract calculates the payment amount based on the quantity of items purchased by the user. The payment amount is calculated by division, and in Solidity, the decimal part of the division result is ignored. If the dividend is smaller than the divisor, the result will be zero. Since the quantity of items purchased by the user can affect the dividend in the payment amount calculation formula, the user can make the payment amount zero by controlling the purchase quantity. In this case, if the `salePrice` is set less than `GU`, the `staker` can buy tokens without paying any price. For example, if the `salePrice` is less than `GU`, the `amount * salePrice` can less than `GU`. So the `_purchaseAmount` may be 0. The hacker can utilize this to buy token without paying.
Open the reportWhen transferring deflationary ERC20 tokens, the input amount may not be equal to the received amount due to the charged transaction fee. For example, if a user sends 100 deflationary tokens (with a 10% transaction fee), only 90 tokens actually arrived to the contract. However, a failure to discount such fees may allow the same user to withdraw 100 tokens from the contract, which causes the contract to lose 10 tokens in such a transaction. Reference: https://thoreum-finance.medium.com/what-exploit-happened-today-for-gocerberus-and-garuda-also-for-lokum-ybear-piggy-caramelswap-3943ee23a39f ```solidity=225 saleToken.transferFrom(msg.sender, address(this), _amount); ``` - Transferring tokens by `_amount`. ```solidity=224 saleTotalAmount += _amount; ``` - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees. --- ```solidity=241 stakingToken.transferFrom(msg.sender, address(this), _amount); ``` - Transferring tokens by `_amount`. ```solidity=238 _pos.stakingAmount += _amount; ``` - The `_amount` appears to be used for bookkeeping purposes without compensating the potential transfer fees.
Open the reportIn the function `releaseVestedTokensMulti`, if the sum of the vested amount and the vesting amount is equal to the specified buy amount (_pos.buyAmount), the token owner will be unable to claim the vested tokens. ```solidity=388 if (_pos.vestedAmount + _vestingAmount >= _pos.buyAmount) { continue; } ```
Open the reportThe `stake` function lacks an allowance check, in contrast to the `depositSaleTokens` function, where a similar check is implemented. ```solidity=222 require(saleToken.allowance(msg.sender, address(this)) >= _amount, 'Token allowance too low'); ```
Open the reportIn the `participateInSale` function, users can join the token sale by paying with ETH if the purchase token address is zero. If the purchase token address is not zero, users must pay with ERC20 tokens. ```solidity=314 if (address(purchaseToken) == address(0)) { // ETH require(msg.value == _purchaseAmount, 'Purchase amount is not matched'); } else { // ERC-20 require(purchaseToken.balanceOf(msg.sender) >= _purchaseAmount, 'Purchase token amount is not enough'); require( purchaseToken.allowance(msg.sender, address(this)) >= _purchaseAmount, 'Purchase token amount is not approved' ); purchaseToken.transferFrom(msg.sender, address(this), _purchaseAmount); } ``` However, there is no validation on the `msg.value` during the payment for sale tokens with ERC20 tokens. If users mistakenly input both ETH and ERC20 tokens, the ETH may potentially be locked in the contract.
Open the reportThe issue lies within the `releaseVestedTokensMulti` function where the `saleToken` is an `ERC777` token. ```solidity=374 function releaseVestedTokensMulti(address[] memory _users) public { require(block.timestamp >= saleEndTs, 'Sale is not ended'); require(block.timestamp >= vestingStartTs, 'Vesting is not started'); for (uint i = 0; i < _users.length; i++) { Position storage _pos = positions[_users[i]]; if (_pos.stakingAmount == 0 || _pos.buyAmount == 0) { continue; } uint _vestingAmount = getVestingAmount(_users[i]); if (_vestingAmount == 0) { continue; } if (_pos.vestedAmount + _vestingAmount >= _pos.buyAmount) { continue; } _pos.vestedAmount += _vestingAmount; if (_pos.vestedAmount == _pos.buyAmount) { removeSoldUser(_users[i]); } saleToken.transfer(_users[i], _vestingAmount); emit TokensVested(_users[i], _vestingAmount); } } ``` If the user is a malicious contract that either lacks a `tokensReceived()` function or explicitly rejects the saleTokens, this will lead to the entire transaction being reverted. This means that users will be unable to receive the sale tokens - a situation known as a denial of service (DoS) attack. A similar issue also exists in the function `unstakeMulti`.
Open the reportThe linked variable is redundantly stored in `storage`, as `storage` look-ups make the gas price higher. Storage variable is good for following rules: - The first item in a storage slot is stored lower-order aligned. - Value types use only as many bytes as are necessary to store them. - If a value type does not fit the remaining part of a storage slot, it is stored in the next storage slot. - Structs and array data always start a new slot and their items are packed tightly according to these rules. - Items following struct or array data always start a new storage slot.
Open the reportState variables that never change should be declared as `constant` to save gas. ```solidity=53 uint GU = 10 ** 18; ``` - `GU` should be declared `constant`.
Open the reportThe return values of the `transfer()` and `transferFrom()` calls in the smart contract are not checked. Some ERC-20 tokens' transfer functions return no values, while others return a bool value, they should be handled with care. If a function returns `false` instead of reverting upon failure, an unchecked failed transfer could be mistakenly considered successful in the contract. ```solidity=225 saleToken.transferFrom(msg.sender, address(this), _amount); ``` --- ```solidity=241 stakingToken.transferFrom(msg.sender, address(this), _amount); ``` --- ```solidity=256 stakingToken.transfer(_user, _pos.stakingAmount); ``` --- ```solidity=274 stakingToken.transfer(_users[i], _pos.stakingAmount); ``` --- ```solidity=324 purchaseToken.transferFrom(msg.sender, address(this), _purchaseAmount); ``` --- ```solidity=368 saleToken.transfer(msg.sender, _vestingAmount); ``` --- ```solidity=396 saleToken.transfer(_users[i], _vestingAmount); ``` --- ```solidity=414 purchaseToken.transfer(owner(), _purchasedAmount); ``` --- ```solidity=418 saleToken.transfer(owner(), _unsoldAmount); ```
Open the reportThe linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.
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="nvirworld"></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