As of 14 Oct 2026, NetMind has 1 audit report on Trustblock, by Quantstamp, dated 4 Aug 2024. That report lists no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Statuses are the security firms’ own. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| NetMind | Security firm: Quantstamp | Date: | Scope: Repository at commit d52de61 | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 2 | 3 |
| Medium | 0 | 5 | 3 |
| Low | 1 | 7 | 4 |
This protocol contains significant centralization, as some roles in the system have excessive privileges and key protocol actions take place in undocumented off-chain processes. We include a non-exhaustive list of the most striking examples of this centralization below: - Depositors into `FixedLock` will only earn rewards if the team deposits enough Rewards to fund reward claims. If both Alice and Bob were to stake for the same amount of time, but the team deposited rewards that were only sufficient for Alice to claim, and Alice claimed first, Bob would have no way of claiming his rewards until the team deposits again. - On-chain, there is no synchronization between the stakes made in either `Pledge` or `LongTermPledge` and the rewards that can be claimed in `RewardContract`. The smart contracts enforce no guarantee of rewards for stakers, and when rewards are claimed, there is no consideration of the existing stake that was made on-chain. As a result, staked users looking to claim rewards at `RewardContract.withdrawToken()` are totally reliant on the nodes defined in `Pledge` (more specifically, `conf.Staking()`) acting honestly to grant the expected rewards to the staker. It is worth emphasizing here that the reward calculation is done in an undocumented process, off-chain. - Documentation from the NMT team suggests that `Purchase.swapToken()` is a method allowing users to purchase training services through USDC. During discussions during the audit, the NMT team described that a user purchasing Training Tasks with fiat will have their account managed off-chain instead of in the `AccountManage` contract. Native NMT depositors have access to increase or decrease their balance, providing significantly more account control to users who purchase using fiat. If a user were to `Purchase.swapToken()` to provide an increase to a balance for Training Tasks, this user must completely trust the NMT Team to honor their purchased amount and adequately manage the account off-chain. - `Conf.wards` authorized accounts have excessive privileges, and a malicious ward address can compromise the system (see NETM-3).
Open the report`Conf.wards` is a mapping containing privileged addresses that can invoke `Conf.file()` to assign values to crucial protocol state such as contract addresses, privileged executors, and fees. Additionally, any privileged `ward` has the ability to `rely()` or `deny()` another address to be either included or removed from this privileged mapping. This is an excessive privilege that can be abused by a single malicious `ward`. Consider if a `ward` address was compromised and needs to have their privileges removed, which means the `deny()` function has to be invoked. This malicious ward could, in theory, never be denied privilege by continuously front-running the `deny()` transaction with a `rely()` transaction to delegate that privilege to another address they own. Further, this compromised ward could deny every other ward in the system, becoming the sole entity that can configure the protocol and reconfigure all protocol values to something malicious.
Open the report`AccountManage.updateAccount()` does not consider whether the account being updated has an existing balance, or has an active job associated with it in `TrainingTask.jobMsg`. As a result, the `Conf.accountManageExecutor` has the power to prevent a depositing user from withdrawing excess funds that are not actively frozen for a Training Task. This could be done by updating the account to either the zero address or some other address that is not controlled by the original depositor. Since debiting existing jobs does not consider the assigned address of the account being used to fund the job, existing jobs can still be credited to completion; however, any remaining funds following the completion of the job will be inaccessible to the original depositor if updated.
Open the reportThe `burn()` and `move()` functions in the contract exhibit nearly identical logic, with the primary distinction being the use of separate nonces (`BurnNonce` and `MoveNonce`). Both functions perform similar checks on the nonce, amount, expiry, and signatures. The only substantial difference is the destination of the transferred amount: the `move()` function transfers the amount to `RewardContract`, whereas the `burn()` function transfers the amount to the zero address. This lack of distinction between the two functions could lead to potential misuse and confusion, especially if the nonces become aligned and the signatures have not expired. The reliance on nonce alignment as the sole protection mechanism is fragile and could be compromised, leading to unintended burning of funds that were meant to be moved to the reward pool, or vice versa.
Open the reportThe NetMind protocol lacks sufficient technical documentation and testing. The complex and interconnected nature of the codebase requires thorough documentation to explain each contract's purpose and how they interact. Currently, there is no technical specification detailing the system's overall operations. Without documentation, functionality and requirements typically need to be inferred from client-authored test suites. However, the Netmind protocol does not have test suites, making it impossible to validate the intended behavior and implementation during the audit. Extensive communication with the client was necessary to clarify design decisions and intended behavior. However, this level of communication is insufficient to fully compensate for the lack of documentation, resulting in significant challenges during the audit. Furthermore, while off-chain and centralized components are out of scope for this audit, the absence of thorough testing for on-chain components increases operational risk. Ensuring proper configuration and functionality of the smart contracts is crucial, yet no post-deployment checks are in place.
Open the reportThe functions `buy_P()`, `sell_P()`, `addLiquidity_P()`, and `removeLiquidity_P()` allow the manager to create proposals for liquidity operations. However, these proposals are based on parameters that can be manipulated through changes in the Pancake pair pool, making the proposal parameters vulnerable to slippage and sandwich attacks. Key issues include: 1. Parameter Manipulation: The calculation of `amountMinOut` for `buy_P()` and `sell_P()`, and liquidity proportions in `removeLiquidity_P()` and `addLiquidity_P()` , can be manipulated by external actors. 2. Inadequate Slippage Protection: The slippage parameter is a percentage of potentially manipulated values, providing insufficient protection. 3. Lack of Proposal Rejection Mechanism: There is no mechanism to reject a proposal based on manipulated parameters; the manager can only wait for the proposal to expire.
Open the reportIn conversations with the NMT team, they described that the Rewards received by a user would be done off-chain. However, there will be snapshots done for rewards at a recurring timestamp, once daily. This creates the opportunity for users to time their calls to `Pledge.stake()` immediately before the off-chain snapshot is taken and call `Pledge.cancleStake()` immediately after to capture staking rewards while only having to stake for very minimal time. The team has acknowledged that this will be of minimal impact because rewards would be so small, however, if the rewards exceed the gas cost of the transaction, it is indeed profitable for a user to do this.
Open the reportCurrently, the [deployed `FixedLock` contract](https://nmscan.netmind.ai/address/0x608B25EBd307BC41eD29106644fdC5b5cBA0dd53_) has the rewards configured to a zero value with no way to reassign the value. The only way for rewards to be enabled at this address would be if the implementation behind this proxy was updated to allow for an update to `rewardProportion`, and that value was updated to be non-zero.
Open the reportThe voting system programmed in the `Governor` contract defines the quorum as a hardcoded amount of native NMT to be passed when voting instead of representing an actual quorum among voters. Therefore, if a vote is made with the required amount of `quorumVotes` NMT, it will immediately pass. This does not represent a quorum in this context, as a quorum is usually required to be a set number of votes to be cast before the vote can be considered settled. Additionally, any votes against a proposal in `Governor` are not checked against the defined quorum. We also note here that in `Governor`, all proposals that pass require the users to trust that the NMT team will honor whatever the context of the proposal may be, off-chain.
Open the report<br> 1. All proposal and vote functionality in `LiquidityFundsManage` is guarded by the `OnlyManager` modifier, which reverts the execution if the caller is not an address included in the `Managers` array. When the `OnlyManager` modifier is executed, the entire `Managers` array is iterated over, checking every element to determine if the `user` address is present. Not only is this method gas-inefficient, but it could cause transactions to run out of gas and fail if the `Managers` array is sufficiently large and the `user` address is at the end of the array. 1. When `Payment.distribute()` and `Payment.agentDistribute()` are called, the `gpu_provider` checked against the whitelist via the function `_isInWhitelist()`. During the execution of `_isInWhitelist()`, the entire `whitelist` array is iterated over, checking every element to determine if the `addr` address is present. Not only is this method gas-inefficient, but it could cause transactions to run out of gas and fail if the `whitelist` array is sufficiently large and the `addr` address is at the end of the array. 1. In the function `FinanceToken.queryUserMsg()`, the `userInfo` list grows indefinitely for a user's address as purchases are made. If a user has made a sufficiently high number of purchases, `queryUserMsg()` would run out of gas when looping and revert the execution.
Open the reportThe `baseProxy` contract can `receive()` native tokens despite no way to withdraw them, leading to the potential of funds locked in a contract. The majority of contracts in this system sit behind one of these proxies. Therefore, if funds needed to be withdrawn from a contract that doesn't already include a function to withdraw, the implementation would have to be upgraded to retrieve the potentially locked funds.
Open the reportWhen `Payment.agentDistribute()` is executed, the caller specifies two values for both GPU fees (`gpu_fee` and `gpu_nmt`) and platform fees (`platform_fee` and `platform_nmt`). While all four values are included in the distribution digest, the agent payment's `distributed` amount is incremented by the sum of `gpu_fee` and `platform_fee`, but the actual distributed amount (performed by `FiatoSettle.distribute()`) is `gpu_nmt` and `platform_nmt`. Therefore, if `gpu_fee` does not equal `gpu_nmt` or `platform_fee` does not equal `platform_nmt`, the agent payment's `distributed` state would deviate from what was distributed from the `FiatoSettle` contract.
Open the reportThe `CrossChain.withdrawChargeAmount()` function uses the `transfer()` method to transfer ERC20 tokens without checking the return value. This approach has multiple issues: 1. **Silent Failures:** Some ERC20 tokens may fail the transfer by returning `false` instead of reverting the transaction, leading to a scenario where the function appears to succeed even if the token transfer fails. 2. **Incompatibility:** Not all ERC20 tokens strictly follow the `IERC20` interface. For instance, USDT's implementation can cause the contract to revert during a transfer because the `transfer` function does not return a value upon a successful transfer.
Open the reportWhen the proxy contracts are deployed, they do not execute their implementation contract's `initialize()` function. Therefore, immediately following deployment, anyone can interact with the contract while the state variables remain uninitialized, which can lead to unintended behavior. Further, the first person to call the contract's initialize function can set important state variables with arbitrary data. A new proxy must be deployed and initialized with the correct data in such a case.
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="netmind"></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