Nut DeFi - Audit

CertiK1 address on BNB Chain

CertiK reviewed Nut DeFi (Nut DeFi - Audit) on 20 Jan 2025: 33 findings, 0 critical; 18 marked fixed, 3 acknowledged, 12 open.

No open critical findings
Open the reportOpen Nut DeFi’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings33

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

SeverityOpenAcknowledgedFixed
Critical000
High030
Medium608
Low6010
MediumFee Whitelist Address and Fund Address Has ExcessOpen

In the contract, functions modified with `onlyWhiteList` can be validated as long as the address is in the fee whitelist or fund address: `nut_live.sol` ```solidity=402 modifier onlyWhiteList() { address msgSender = msg.sender; require(_feeWhiteList[msgSender] || msgSender == fundAddress || msgSender == _owner, "nw"); _; } ``` Therefore, the following functions can be executed as long as the address is in the fee whitelist: - setFundAddress() - setFeeWhiteList() - setSwapPairList() - setLPHoldCondition() - setExcludeLPProvider() - setRewardGas() - startTrade() - setLPRewardTimeDebt() - setRemoveLPFee() - setBuyFee() - setSellFee() the following functions can be executed as long as the address is fund address: - setAutoLPBurnSettings() - setFundAddress() - setFeeWhiteList() - setSwapPairList() - setLPHoldCondition() - setExcludeLPProvider() - setRewardGas() - startTrade() - setLPRewardTimeDebt() - setRemoveLPFee() - setBuyFee() - setSellFee() The following functions can be executed if the address is both the fund address and the fund address is included in the fee whitelist: - updateLPAmount() - setExcludeReward() However, these are core permission functions, and the fee whitelist or fund address should not have such high-level permissions to execute them. Additionally, if the project team renounces ownership, the contract can still be controlled via previously added fee whitelist addresses or the fund address, which poses a certain centralization risk.

MediumIncorrect Fee Charges on Liquidity Addition Due tOpen

In the `NutToken` contract, the system mistakenly categorizes the action of adding liquidity (LP) as a **selling action**, which results in an improper application of transaction fees. Specifically: When a user adds liquidity, the contract charges a **2% fee** on the transferred `NUT` tokens: - **1% is sent to the dead address**. - **1% is sent to the NUT contract address**. According to the contract's intended design, **adding liquidity should not incur any fees**. The action of adding liquidity (transferring tokens to the liquidity pool) is not a "sell" transaction but rather a contribution to the pool, which benefits the ecosystem. This discourages users from participating in liquidity provision, potentially harming the liquidity of the `NUT` token on decentralized exchanges.

MediumLiquidity Provider May Receive No Reward and PoteOpen

When the amount of NUT tokens accumulated in the NUT token contract reaches a certain threshold, a swap will be triggered during the transfer of NUT tokens. However, this swap will update some variables, leading to unintended updates in the state of the pair or NUT token: 1. If USDT is transferred first, then NUT is transferred (which triggers a swap), and then LP tokens for the mainPair are minted (e.g., using `swapRouter`'s `addLiquidity()` to add liquidity), the initially transferred USDT will be synced to the pair's reserve during the swap, resulting in a minting of 0 LP tokens. Due to the pair's LP minting check, the transaction will fail. 2. If `swapRouter`'s `addLiquidity()` is used and a swap is triggered during the transfer of NUT tokens, the `transfer` of NUT will set the previously transferred `_lastMaybeLPAddress` to 0, and since `mint()` for the pair hasn't been called yet, the LP token amount will be 0. **The user adding liquidity will not be added as an LP provider, causing a legitimate liquidity provider to not receive rewards.**

MediumReward Calculation Time Will Not Stop Due to UserOpen

When a user successfully adds liquidity for the first time, the reward timestamp `_lastLPRewardTimes` will change from 0 to the current time of adding liquidity, and the user will be added to the LP provider list: `nut_live.sol` ```solidity=540 if (lastMaybeAddLPAddress != address(0)) { _lastMaybeAddLPAddress = address(0); uint256 lpBalance = IERC20(mainPair).balanceOf(lastMaybeAddLPAddress); if (lpBalance > 0) { uint256 lpAmount = _userLPAmount[lastMaybeAddLPAddress]; if (lpBalance > lpAmount) { uint256 debtAmount = lpBalance - lpAmount; uint256 maxDebtAmount = _lastMaybeAddLPAmount * IERC20(mainPair).totalSupply().div(_balances[mainPair]); if (debtAmount > maxDebtAmount) { excludeLpProvider[lastMaybeAddLPAddress] = true; } else { _addLpProvider(lastMaybeAddLPAddress); _userLPAmount[lastMaybeAddLPAddress] = lpBalance; uint256 blockTime = block.timestamp; if (0 == _lastLPRewardTimes[lastMaybeAddLPAddress]) { _lastLPRewardTimes[lastMaybeAddLPAddress] = blockTime; } } } } } ``` However, when a user subsequently removes all their liquidity, there is no mechanism to remove the user from the LP provider list, and the reward calculation time does not stop : `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; } } ``` This will result in the user's reward time continuing to accrue even when they do not hold any LP tokens. As long as they hold a certain amount of LP tokens before the reward cycle ends, they can claim the rewards for that period (5 days). However, since the user did not hold tokens during that time, the reward time should not be calculated.

MediumTransferring Tokens to the Pair May Be MisidentifOpen

After transferring tokens to the pair, the following logic can be used to mark the `from` address as `_lastMaybeAddLPAddress` and the amount as `_lastMaybeAddLPAmount`: `nut_live.sol` ```solidity=639 if (from != address(this)) { if (to == mainPair) { _lastMaybeAddLPAddress = from; _lastMaybeAddLPAmount = amount; } ``` However, by transferring a certain amount of LP tokens to the `from` address before the next transfer, this sell token transaction can be misidentified as an add liquidity transaction, and the `from` address will be added to the `lpProviders` array to be eligible for rewards, even though the user did not actually add liquidity: `nut_live.sol` ```solidity=540 if (lastMaybeAddLPAddress != address(0)) { _lastMaybeAddLPAddress = address(0); uint256 lpBalance = IERC20(mainPair).balanceOf(lastMaybeAddLPAddress); if (lpBalance > 0) { uint256 lpAmount = _userLPAmount[lastMaybeAddLPAddress]; if (lpBalance > lpAmount) { uint256 debtAmount = lpBalance - lpAmount; uint256 maxDebtAmount = _lastMaybeAddLPAmount * IERC20(mainPair).totalSupply().div(_balances[mainPair]); if (debtAmount > maxDebtAmount) { excludeLpProvider[lastMaybeAddLPAddress] = true; } else { _addLpProvider(lastMaybeAddLPAddress); _userLPAmount[lastMaybeAddLPAddress] = lpBalance; uint256 blockTime = block.timestamp; if (0 == _lastLPRewardTimes[lastMaybeAddLPAddress]) { _lastLPRewardTimes[lastMaybeAddLPAddress] = blockTime; } } } } ``` This will introduce a security risk to the contract.

MediumUser and Inviter Reward Calculation Time Is ShortOpen

First, the time for calculating staking reward after the user adds liquidity is not the time of addition, but rather the next transfer: `nut_live.sol` ```solidity=540 if (lastMaybeAddLPAddress != address(0)) { _lastMaybeAddLPAddress = address(0); uint256 lpBalance = IERC20(mainPair).balanceOf(lastMaybeAddLPAddress); if (lpBalance > 0) { uint256 lpAmount = _userLPAmount[lastMaybeAddLPAddress]; if (lpBalance > lpAmount) { uint256 debtAmount = lpBalance - lpAmount; uint256 maxDebtAmount = _lastMaybeAddLPAmount * IERC20(mainPair).totalSupply().div(_balances[mainPair]); if (debtAmount > maxDebtAmount) { excludeLpProvider[lastMaybeAddLPAddress] = true; } else { _addLpProvider(lastMaybeAddLPAddress); _userLPAmount[lastMaybeAddLPAddress] = lpBalance; uint256 blockTime = block.timestamp; if (0 == _lastLPRewardTimes[lastMaybeAddLPAddress]) { _lastLPRewardTimes[lastMaybeAddLPAddress] = blockTime; } } } } } ``` At the time when the user is supposed to receive rewards, not all the rewards accumulated during this period are distributed to the user and inviter; only the rewards for a 5-day period are distributed: `nut_live.sol` ```solidity=870 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); } } ``` As a result, the rewards for the excess time will not be included in the calculation. Additionally, an attacker can increase the length of the LP provider array by mint small amounts of LP with multiple addresses in the main pair. This would delay the user's turn to receive the staking rewards, resulting in further staking reward losses.

Low`_transfer` of `AbsToken` requires refactoringOpen

The call to `_transfer()` of `AbsToken` is overcomplicated, involving dozens of logic, and some actions are performed on different levels.

LowDuring Flash Loan in Pair, Repaying NUT Tokens MaOpen

During a flash loan in the pair, when repaying NUT tokens, the `autoBurnLiquidityPairTokens` function may automatically burn tokens in the pair. In this case, since both the `swap` and `sync` functions of the pair have reentrancy locks, the transaction will revert when executing `mainPair.sync()`, causing the transaction to fail.

LowMissing Emit EventsOpen

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

LowPotential Out-of-Gas ExceptionOpen

If the value of `gasleft()` outside the `while` loop is less than the passed parameter `gas`, the `while` loop may throw an `out-of-gas` exception. ``` solidity=126 while (gasUsed < gas && iterations < shareholderCount) { if (currentIndex >= shareholderCount) { currentIndex = 0; } if (shouldDistribute(shareholders[currentIndex])) { distributeDividend(shareholders[currentIndex]); } gasUsed = gasUsed.add(gasLeft.sub(gasleft())); gasLeft = gasleft(); currentIndex++; iterations++; } ```

Show 2 more
LowPotential Sandwich AttacksOpen

A sandwich attack can occur when an attacker notices a transaction involving token swaps or liquidity additions that lacks slippage restrictions or minimum output amount settings. The attacker can exploit this by frontrunning the transaction to purchase one of the assets, thereby manipulating the exchange rate, and then backrunning the transaction to sell the asset for a profit. The referenced functions are invoked without setting slippage restrictions or minimum output amounts, making transactions that trigger these functions particularly vulnerable to sandwich attacks, especially when large input amounts are involved.

LowThird Party DependencyOpen

The contract is serving as the underlying entity to interact with one or more third party protocols. The scope of the audit treats third party entities as black boxes and assume their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets. In addition, upgrades of third parties can possibly create severe impacts, such as increasing fees of third parties, migrating to new LP pools, etc. ```solidity=391 ISwapRouter public immutable _swapRouter; ``` - The contract `AbsToken` interacts with third party contract with `ISwapRouter` interface via `_swapRouter`. --- ```solidity=392 address public immutable _mainPair; ``` - The contract `AbsToken` interacts with third party contract with `ISwapPair` interface via `_mainPair`.

Contracts in scope1

AddressChainSource code
0x29fdf61b4ab5a4af38c745d4e1ef282525d4978dChain: BNB ChainSource code: Verified source

Part of Nut DeFi’s security record

Nut DeFi
Audits1 audit by 1 firmLast audit Jan 2025, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open Nut DeFi’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