As of 3 Nov 2026, Nut DeFi has 1 audit report on Trustblock, by CertiK, dated 20 Jan 2025. That report lists no open critical findings. It also holds 1 deployed contract on 1 chain.
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 |
|---|---|---|---|---|---|
| Nut DeFi - Audit | Security firm: CertiK | Date: | Scope: 1 address on BNB Chain | 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 | 0 | 3 | 0 |
| Medium | 6 | 0 | 8 |
| Low | 6 | 0 | 10 |
In `findRoot`, there is no limit on the depth of the upper-level inviter search: ```solidity=912 function findRoot(address user) public view returns (bool) { address current = user; while (current != address(0)) { address inviter = _userInfo[current].inviter; if (inviter == receiveAddress) { return true; } if (inviter == address(0)) { break; } current = inviter; } return false; } ``` When there are too many upper-level inviters, `findRoot` can consume a large amount of gas from the user, potentially leading to DoS vulnerability.
Open the reportIn the `_transfer` function, the following code contains flaws and is vulnerable to attacks: `nut_live.sol` ```solidity=545 if (lpBalance > lpAmount) { uint256 debtAmount = lpBalance - lpAmount; uint256 maxDebtAmount = _lastMaybeAddLPAmount * IERC20(mainPair).totalSupply().div(_balances[mainPair]); if (debtAmount > maxDebtAmount) { excludeLpProvider[lastMaybeAddLPAddress] = true; ``` Firstly, `maxDebtAmount` uses division before multiplication, while PancakeSwap uses multiplication before division in its calculation: `nut_live.sol` ```solidity=547 uint256 maxDebtAmount = _lastMaybeAddLPAmount * IERC20(mainPair).totalSupply().div(_balances[mainPair]); ``` Due to rounding, this may cause `maxDebtAmount` to be smaller than the actual value, incorrectly setting `lastMaybeAddLPAddress` as `excludeLpProvider`. Secondly, for any user adding liquidity, an attacker can transfer LP from the main pair (with any amount greater than 0) to the user between their liquidity addition and the next NUT transfer. This would increase the `debtAmount`, causing it to exceed `maxDebtAmount` and prevent the user from receiving staking rewards. Finally, during remove liquidity, due to calculation issues, the liquidity might be calculated as greater than the actual amount, causing `_userLPAmount[to]` to be smaller than the actual value: `nut_live.sol` ```solidity=623 if (isRemoveLP) { if (!_feeWhiteList[to]) { takeFee = true; uint256 liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - 1); if (from != address(_swapRouter)) { liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - amount - 1); } require(_userLPAmount[to] >= liquidity, ">uLP"); _userLPAmount[to] -= liquidity; } } ``` If the user adds liquidity after this, it may cause the `debtAmount` to be larger than the actual value, causing it to exceed `maxDebtAmount` and incorrectly setting `lastMaybeAddLPAddress` as `excludeLpProvider`.
Open the reportIn the transfer function, there are two instances where the code logic checks whether it is in the process of removing liquidity. The first instance requires that the `from` address is the main pair and the `to` address is not in the fee whitelist. At this point, if the USDT amount in the pair is less than or equal to the amount recorded in the reserves, it is considered as a remove liquidity action : `nut_live.sol` ```solidity=595 isRemoveLP = _isRemoveLiquidity(); ``` `nut_live.sol` ```solidity=988 function _isRemoveLiquidity() internal view returns (bool isRemove){ ISwapPair mainPair = ISwapPair(_mainPair); (uint r0,uint256 r1,) = mainPair.getReserves(); address tokenOther = _usdt; uint256 r; if (tokenOther < address(this)) { r = r0.div(10 ** 12); } else { r = r1.div(10 ** 12); } uint256 bal = (IERC20(tokenOther).balanceOf(_mainPair)).div(10 ** 12); isRemove = r >= bal; } ``` However, the PancakeSwap swap function allows flash loans. During a flash loan, both USDT and NUT tokens may be reduced simultaneously and transferred to the contract. The contract only needs to repay the loan and pay the fee in the `pancakeCall` to maintain the K value greater than or equal to its original value. This situation would also be recognized as remove liquidity. In this case, if the user has liquidity in the pair, the `_userLPAmount` recorded in the token contract will be reduced, thereby decreasing the reward. If not, the transaction may fail. `nut_live.sol` ```solidity=623 if (isRemoveLP) { if (!_feeWhiteList[to]) { takeFee = true; uint256 liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - 1); if (from != address(_swapRouter)) { liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - amount - 1); } require(_userLPAmount[to] >= liquidity, ">uLP"); _userLPAmount[to] -= liquidity; } } ``` Meanwhile, the transaction fee will be recognized as remove LP and will be fully destroyed. The other instance logic requires that the `from` address is `address(_swapRouter)`. However, the router will only actively transfer NUT tokens out of the contract through the `removeLiquidityETHSupportingFeeOnTransferTokens` function, which only supports ETH and other token pairs, i.e., ETH and NUT pairs. Therefore, this operation does not refer to remove liquidity for the main pair (USDT + NUT) and should not set `isRemoveLP` to true: `nut_live.sol` ```solidity=619 if (from == address(_swapRouter)) { isRemoveLP = true; } ``` `PancakeRouter.sol` ```solidity=172 function removeLiquidityETHSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) public virtual override ensure(deadline) returns (uint amountETH) { (, amountETH) = removeLiquidity( token, WETH, liquidity, amountTokenMin, amountETHMin, address(this), deadline ); TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this))); IWETH(WETH).withdraw(amountETH); TransferHelper.safeTransferETH(to, amountETH); } ``` At the same time, the liquidity calculation logic for `from == address(_swapRouter)` should be modified: `nut_live.sol` ```solidity=623 if (isRemoveLP) { if (!_feeWhiteList[to]) { takeFee = true; uint256 liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - 1); if (from != address(_swapRouter)) { liquidity = (amount * ISwapPair(_mainPair).totalSupply() + 1).div(balanceOf(_mainPair) - amount - 1); } require(_userLPAmount[to] >= liquidity, ">uLP"); _userLPAmount[to] -= liquidity; } } ``` Otherwise, removing liquidity operations for the ETH-NUT pair will also be treated as operations for the main pair (USDT-ETH), reducing the user's liquidity amount(`_userLPAmount`) and causing error in the liquidity statistics within the contract.
Open the reportIn the `processStakingRewards` function, when calculating the reward cycle, `progressLPBlock` records the BSC block height, while `lpRewardTimeDebt` is a time interval in seconds: `nut_live.sol` ```solidity=378 uint256 public lpRewardTimeDebt = 5 days; ``` `nut_live.sol` ```solidity=830 if (progressLPBlock + lpRewardTimeDebt > block.number) { ``` Since the BSC block time is 3 seconds per block, the actual reward cycle is not 5 days but 3 * 5 = 15 days, which is three times longer than the setting time.
Open the reportThe `_removeLPFee` variables in the contract have no set limits, allowing fees to potentially reach 100%. If this occurs, users would receive no tokens from the contract, resulting in a complete loss of their investment.
Open the reportThe `autoBurnLiquidityPairTokens` function in the `NUT` contract introduces a **liquidity burn mechanism**, which reduces the circulating supply of LP tokens. While this mechanism is intended to increase scarcity and stabilize the token price, it inadvertently creates **arbitrage opportunities**, especially when `_swapPairList` includes multiple trading pairs. When `_swapPairList` includes multiple trading pairs (e.g., `NUT/USDT` and `NUT/WETH`), the arbitrage becomes potentially more profitable: 1. The attacker identifies that a burn operation is imminent on one pair (e.g., `NUT/USDT`). 2. The attacker buys `NUT` on a **different pair** (e.g., `NUT/WETH`) where the price is still unaffected by the burn. 3. The attacker sells a few `NUT` on `NUT/WETH` and burn operation will occur on `NUT/USDT`. As a result, the price of `NUT` increases in the `NUT/USDT` pair. 4. The attacker sells the purchased `NUT` tokens at the higher price to gain some profits.
Open the reportSome restrictions can be bypassed by swapping tokens and removing liquidity to the router and then getting tokens from the router. When the tokens are transferred to the router, the use uses the feature of the router function ***`removeLiquidityETHSupportingFeeOnTransferTokens`***, which will send all the specified tokens in the router to a specified address. Because the router is not supposed to receive tokens for any user other than remove liquidity.
Open the reportIn `processStakingRewards`, after a certain time period, the function will iterate through the `lpProviders` array and settle the rewards for the addresses it encounters: `nut_live.sol` ```solidity=852 while (gasUsed < gas && iterations < shareholderCount) { if (currentLPIndex >= shareholderCount) { currentLPIndex = 0; } shareHolder = lpProviders[currentLPIndex]; (uint256 userUSDTContribution,) = getUserLiquidityContribution(shareHolder); if (!excludeLpProvider[shareHolder]) { pairBalance = mainPair.balanceOf(shareHolder); lpAmount = _userLPAmount[shareHolder]; if (lpAmount < pairBalance) { pairBalance = lpAmount; } // Only reward if USDT equivalent meets 30 USDT minimum and reward interval has passed if (userUSDTContribution >= lpHoldCondition && block.timestamp > _lastLPRewardTimes[shareHolder] + lpRewardTimeDebt) { // Calculate 0.8% daily reward in USDT, then convert to NUT using pool price uint256 dailyUsdtReward = (userUSDTContribution.mul(DAILY_REWARD_RATE)).div(1000); uint256 rewardAmountInNUT = (dailyUsdtReward.mul(rNut)).div(rUsdt); rewardAmountInNUT *= 2; // Calculate total reward for the 5-day interval rewardAmount = rewardAmountInNUT.mul(5); if (rewardAmount > 0) { _lastLPRewardTimes[shareHolder] = block.timestamp; processInviterRewards(shareHolder, rewardAmount); _rewardTransfer(shareHolder, rewardAmount); } } } gasUsed += (gasLeft - gasleft()); gasLeft = gasleft(); currentLPIndex++; iterations++; } ``` However, the `lpProviders` array can be artificially extended by minting main pair LP tokens. When the array grows too long and the gas required to iterate through it exceeds the reward gas limit, users will not receive the corresponding rewards for each cycle. This situation causes users to be unable to receive their rewards for an extended period.
Open the reportWhen transferring between two non-fee whitelist addresses using transferFrom, if the from address has less than 100 wei remaining after the transfer, the transfer amount will be reduced so that the user is left with at least a 100 wei balance: `nut_live.sol` ```solidity=562 if (!_feeWhiteList[from] && !_feeWhiteList[to]) { uint256 maxSellAmount; uint256 remainAmount = 10 ** (_decimals - 4); uint256 balance = _balances[from]; if (balance > remainAmount) { maxSellAmount = balance - remainAmount; } if (amount > maxSellAmount) { amount = maxSellAmount; } } ``` However, the transferFrom function will still deduct the original amount of the allowance. `nut_live.sol` ```solidity=513 function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { _transfer(sender, recipient, amount); if (_allowances[sender][msg.sender] != MAX) { _allowances[sender][msg.sender] = _allowances[sender][msg.sender] - amount; } return true; } ```
Open the reportThe contract logic related to `_swapPairList` is tightly coupled with the `_mainPair` (the primary liquidity pool pair, typically NUT/USDT). Specifically, the logic for **buying**, **selling**, and **fee calculations** depends heavily on whether the sender or recipient is part of `_swapPairList`. However, there is no explicit restriction or validation in the contract to ensure that `_swapPairList` only includes the `_mainPair`. This creates a potential issue if other pairs are unintentionally or maliciously added to `_swapPairList`. ```solidity=700 else if (_swapPairList[sender]) {//Buy uint256 usdtAmount = (tAmount.mul(tokenPrice())).div(10**6); require(usdtAmount <= MAX_BUY_AMOUNT, "You can only buy max 20,000 usdt"); require(block.timestamp >= lastSwapTime[recipient] + MIN_SWAP_INTERVAL, "Must wait 2 minutes between transactions"); lastSwapTime[recipient] = block.timestamp; swapFeeAmount = (tAmount.mul(_buyFundFee + _buyDevFee)).div(10000); } else if (_swapPairList[recipient]) {//Sell require(block.timestamp >= lastSwapTime[recipient] + MIN_SWAP_INTERVAL, "Must wait 2 minutes between transactions"); lastSwapTime[recipient] = block.timestamp; isSell = true; swapFeeAmount = (tAmount.mul(_sellReservePoolFee)).div(10000); destroyFeeAmount = (tAmount.mul(_sellDestroyFee)).div(10000); } ``` In the `NUT` contract, the tracked information, such as `tokenPrice` and restrictions (e.g., `MAX_BUY_AMOUNT`, `MIN_SWAP_INTERVAL`), is derived from the `_mainPair` (e.g., NUT/USDT). The logic assumes that the `_swapPairList` corresponds to the main trading pair (`_mainPair`), where price calculations and restrictions are meaningful. If additional pairs (e.g., NUT/ETH or unrelated token pairs) are added to `_swapPairList`, the logic for **buying** and **selling** may break or behave incorrectly.
Open the reportFunctions with names starting with `_` should be declared as `private`/`internal`.
Open the reportThe functions marked as 'view' or 'pure' are unnecessarily restricted by the `onlyWhiteList` modifier. These functions are designed to be read-only, meaning they do not modify the state on the blockchain. However, they are restricted so that only a specific address can call them. It's important to note that even private state variables can be read off-chain, rendering the access restriction on these functions ineffective.
Open the reportAfter updating the relevant information of `lastMaybeAddLPAddress`, it was not reset to zero: ```solidity=551 if (lastMaybeAddLPAddress != address(0)) { uint256 lpBalance = IERC20(mainPair).balanceOf(lastMaybeAddLPAddress); if (lpBalance > 0) { uint256 lpAmount = _userLPAmount[lastMaybeAddLPAddress]; if (lpBalance > lpAmount) { _addLpProvider(lastMaybeAddLPAddress); _userLPAmount[lastMaybeAddLPAddress] = lpBalance; // NCK-22 NCK-20 _lastLPRewardTimes[lastMaybeAddLPAddress] = _getTomorrowsMidnight(); } } _userInfo[lastMaybeAddLPAddress].rewardAmount = calculateStakingRewards(lastMaybeAddLPAddress); } ``` As a result, during transfers that do not interact with the pair, the LP-related information of `lastMaybeAddLPAddress` will still be updated. If a small amount of LP tokens is transferred into `lastMaybeAddLPAddress`, it will trigger the logic inside `lpBalance > lpAmount` and update `_lastLPRewardTimes[lastMaybeAddLPAddress]` to the next natural day’s midnight.
Open the reportThe cited address input is missing a check that it is not `address(0)`.
Open the reportThe linked statements do not affect the functionality of the codebase and appear to be either leftovers from test code or older functionality.
Open the reportAfter the `receiveAddress` transfers the corresponding LP to the user, `batchSetUserInfo` will be called by the owner to update the corresponding `rewardAmount` and `_userLPAmount`: `NuToken.sol` ```solidity=1044 function batchSetUserInfo(address[] memory addr) external onlyOwner { require(addr.length > 0, "No addresses provided"); uint256 ownerLpBalance = 0; for (uint i = 0; i < addr.length; i++) { address user = addr[i]; uint256 lpBalance = IERC20(_mainPair).balanceOf(user); _addLpProvider(user); _userLPAmount[user] = lpBalance; ownerLpBalance += lpBalance; _lastLPRewardTimes[user] = _getTomorrowsMidnight();// NCK-22 _userInfo[user].rewardAmount = calculateStakingRewards(user); } _userLPAmount[receiveAddress] = _userLPAmount[receiveAddress] - ownerLpBalance; _userInfo[receiveAddress].rewardAmount = calculateStakingRewards(receiveAddress); } ``` However, there are several situations where the updated `_userLPAmount[receiveAddress]` may not match the actual LP token balance of `receiveAddress`, such as: 1. The `addr` list contains duplicate addresses, causing the `ownerLpBalance` to be double-counted. 2. The address in `addr` already holds LP tokens before the transfer to `receiveAddress`, which results in the `ownerLpBalance` recording an inflated value. Therefore, it is recommended to update the value of `_userLPAmount[receiveAddress]` to reflect the actual amount of LP tokens held by `receiveAddress`.
Open the reportAfter [EIP-1884](https://eips.ethereum.org/EIPS/eip-1884) was included in the Istanbul hard fork, it is not recommended to use `.transfer()` or `.send()` for transferring native tokens as these functions have a hard-coded value for gas costs making them obsolete as they are forwarding a fixed amount of gas, specifically `2300`. This can cause issues in case the linked statements are meant to be able to transfer funds to other contracts instead of EOAs.
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 report| Address | Chain | Source code |
|---|---|---|
| 0x29fd…978d0x29fdf61b4ab5a4af38c745d4e1ef282525d4978d | Chain: BNB Chain | Source code: Verified source |
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="nut-defi"></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