Metacraft

CertiKRepository at commit dac85fa

CertiK reviewed MUMUBIT TOKEN (Metacraft) on 12 Mar 2025: 71 findings, 0 critical; 57 marked fixed, 14 open.

No open critical findings
Open the reportOpen MUMUBIT TOKEN’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings71

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

SeverityOpenAcknowledgedFixed
Critical000
High202
Medium0011
Low12044
HighCentralization Related RisksOpen

In the contract `MCTPStake`, the role `owner` has authority over the following functions. - `pause()`: Used to pause the contract. - `unpause()`: Used to unpause the contract. - `setWithDuration()`: Used to change the status of `withDuration`. - `setWithdrawAddress()`: Used to create a pending transaction for setting `withdrawAddress`. - `addOwner()`: Used to create a pending transaction for adding a new `validOwner`. - `removeOwner()`: Used to create a pending transaction for removing a existed `validOwner`. In the contract `MCTPStake`, the role `owners` (`validOwner`) has authority over the following functions. - `signTransaction()`: Used to sign/execute the pending transactions of modifying the `withdrawAddress`. - `signUserAdd()`: Used to sign/execute the pending transactions of adding a new `validOwner`. - `signUserDel()`: Used to sign/execute the pending transactions of removing a new `validOwner`. In the contract `MCTPStake`, the role `withdrawAddress` has authority over the following function. - `withdrawFunds()`: Used to withdraw the specified itemToken in the `MCTPStake` contract, but it has a protection to ensure that the remaining token balance should equal or more than the total stake amount of users. Any compromise to the `owner` account or `withdrawAddress` account may allow the hacker to take advantage of this authority, change the status of `withDuration`, pause/unpause the staking operation and withdraw the tokens for reward in the contract. --- In the contract `MetaCraftVNode` the role `owner` has authority over the functions shown in the list below. - `addOwner()`: Initiates a pending transaction to add a new `validOwner`. - `removeOwner()`: Initiates a pending transaction to remove an existing `validOwner`. - `setParameters()`: Initiates a pending transaction to change critical operational parameters, including locking and halting the contract's functionalities. - `setSalesAddress()`: Initiates a pending transaction to update the `feeAddress`. Any compromise to the `owner` account may allow the hacker to take advantage of this authority and initialize a pending transaction. The role `_validOwners` has authority over the functions shown in the list below. - `signSetTransaction()`: Used to sign pending transactions initiated by the `owner`. - `executeSetTransaction()`: Executes pending transactions once the required signatures are collected. Any compromise to the `_validOwners` account may allow the hacker to take advantage of this authority and disrupt the normal execution of critical pending transactions in the contract. The role `publicKey` has the authority to sign the signature for minting Node ERC721 token. - `buyNode`: Used to mint the ERC721 token. Only the `publicKey` can sign the signatures for minting Node ERC721 tokens, acting as the sole authority to authenticate mint requests. Any compromise to the `publicKey` account may allow the hacker to perform the unauthorized minting of tokens.

HighWithdrawal Centralization RiskOpen

The role `withdrawAddress` has the authority to withdraw tokens from the contract. There are no controls over when this function can be run and the amount that can be withdrawn. ```solidity=174 function withdrawFunds(address _itemToken) external whenNotPaused withdrawAddressOnly() { IERC20(_itemToken).safeTransfer(msg.sender, IERC20(_itemToken).balanceOf(address(this))); emit StakeEvents(block.timestamp,msg.sender, "withdrawFunds Coin"); } modifier withdrawAddressOnly() { require(msg.sender == withdrawAddress, "only withdrawer can call this"); _; } ``` A hacker can take advantage of this function to withdraw all assets within the contract.

LowConcerns about `itemToken` type and reward distriOpen

When users stake or distribute items, they specify the `itemToken`. Therefore, we need to check the following points with the team: 1. If there is documentation specifying the `itemToken` type when users stake or claim? Using arbitrary tokens may pose potential risks, such as **Unchecked ERC-20 `transfer()`/`transferFrom()` Call** and **Potential Reentrancy Issue**. 2. The source of the reward token sent to the user when staking or unstaking. If there is no sufficient deposited `itemToken` in the contract, this scenario creates a situation where the payouts for the initial users (like User A) are dependent on the deposits made by the newer users (like User B). If the inflow of new stakes ceases or slows, the contract will not have enough tokens to pay out the rewards or return the stakes, characterizing a Ponzi scheme dynamic.

LowIncompatibility With Deflationary Tokens (Non-staOpen

The project design may not be compatible with non-standard ERC20 tokens, such as deflationary tokens or rebase tokens.

LowInconsistent Requirements in `openStake()`, `unStOpen

The `unStake()` function and `reStake()` function impose a restriction that prevents its execution by contracts, however, such a requirement is not present in the `openStake()` function. Consequently, contracts engaging in staking might encounter difficulties when attempting to unstake or restake their staked tokens. ```solidity function openStake( address _itemToken, uint256 _tokenAmount, uint256 _duration ) public whenNotPaused virtual{ ... } function unStake(address _itemToken,uint256 _tokenAmount,uint256 _duration) public whenNotPaused noReentrancy //anti re-entrancy notContract(msg.sender) //anti contract address virtual{ ... } ```

LowInefficient Memory ParameterOpen

One or more parameters with `memory` data location are never modified in their functions and those functions are never called internally within the contract. Thus, their data location can be changed to `calldata` to avoid the gas consumption copying from calldata to memory. ```solidity=398 function setBaseURI(string memory baseURI_) external { _baseURIextended = baseURI_; } ``` - `setBaseURI` has memory location parameters: `baseURI_`. ```solidity=402 function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual //override(ERC721URIStorage) { require(tokenId <= _tokenIds.current(), "ERC721Metadata: URI set of nonexistent token"); _tokenURIs[tokenId] = _tokenURI; } ``` - `_setTokenURI` has memory location parameters: `tokenURI_`. ```solidity=433 function buyNode( address _to, string memory tokenURI_, address parentAddress //uint256 tokenPrice ){ ... } ``` - `buyNode()` has memory location parameters: `tokenURI_`.

LowMissing Input ValidationsOpen

The `tokenDecimals` variable is used to calculate the mint price for `buyNode()`. However, there is no consistency check between `tokenDecimals` and the provided ERC20 token's decimals during initialization, which may result in an incorrect mint fee if they are misaligned. ```solidity ERC20Token = IERC20(_erc20Token); tokenDecimals = _decimals; ``` --- In the `MCTPStakeNew` contract, the `addOwner` and `removeOwner` never checks if the address to be added is already an owner, nor does it check if the address to be removed is truly an existing owner. It only ensures that the `_owner` is not a zero address. If an address that is already on the owner's list is added, the contract may contain inconsistent or duplicated data. ```solidity function addOwner(address _owner) public { ... require(_owner != address(0),"Zero Address Error!"); ... } function removeOwner(address _owner) public { ... require(_owner != address(0),"Zero Address Error!"); ... } ```

LowPotential Cross-Chain/Cross-Contract Replay AttacOpen

According to the team's message, the team plans to upgrade the contract in the future. The team may need to redeploy the `MCTPvNode` contract to upgrade it since the contract itself is not upgradeable. However, redeploying the contract may introduce new attack vectors. The current signature design in the `MCTPvNode` contract is not properly verified with the current chain ID and the address of the contract, thus allowing attackers to perform replay attacks across chains or contracts (from the old version to the new version). Especially if the team plans to deploy contracts on multiple chains or if the team has deployed multiple versions of contracts, the signature and message hash can be reused in the future.

LowPotential Optimization to Multi-Sign MechanismOpen

To optimize the `MetaCraftVNode` contract for handling administrative actions like adding and removing owners, setting parameters, and updating addresses, we can utilize a generic transaction mechanism. This contract can streamline the process by using a single structure to manage different types of transactions, reducing redundancy and enhancing maintainability. ### Administrative Functions Ensure that administrative functions such as `setOwner`, `setParams`, and similar functions can only be executed by `address(this)`. ```solidity modifier onlySelf() { require(msg.sender == address(this), "Unauthorized: can only be called by the contract itself"); _; } function setOwner(address owner, uint8 status) public onlySelf { _validOwners[owner] = status; } function setParameters(/* Parameters */) public onlySelf { // Implementation for setting parameters } ``` ### Data Structures: - `TransactionType`: An enum to categorize transaction types. - `Transaction`: A struct to hold transaction details, including type, data payload, signature count, and execution status. ```solidity enum TransactionType { AddOwner, RemoveOwner, SetParameter, SetSalesAddress } struct Transaction { TransactionType type; bytes data; uint8 signatureCount; uint256 timestamp; bool executed; } ``` ### Functions - **Transaction Creation**: Function to add transactions with data validation. - **Signing Transactions**: Function for authorized owners to sign pending transactions. - **Executing Transactions**: Function to execute signed transactions using dynamic method invocation. ```solidity mapping(uint => Transaction) private _transactions; uint[] private _pendingTransactions; mapping(uint => mapping(address => bool)) private signatures; modifier notExecuted(uint transactionId) { require(!_transactions[transactionId].executed, "Transaction already executed"); _; } function addTransaction(TransactionType _type, bytes memory _data) public onlyValidOwner{ ... } function signTransaction(uint transactionId) public onlyValidOwner{ } function executeTransaction(uint transactionId) public onlyValidOwner notExecuted(transactionId){ ... } ``` ### Security Measures: - **Function Selector Verification**: Before adding a transaction, verify that the function selector matches the expected function for the transaction type. - **Execution Control**: Ensure that transactions can only be executed once they have the required number of signatures and are verified for correct method invocation. ```solidity function addTransaction(TransactionType _type, bytes memory _data) public onlyValidOwner { // Ensure data is at least 4 bytes long require(_data.length >= 4, "Data too short for a valid function selector"); require(isValidFunctionSelector(_type, bytes4(_data)), "Invalid function selector for transaction type"); ... } function executeTransaction(uint transactionId) onlyValidOwner notExecuted(transactionId) { ... (bool success,) = address(this).call(_transactions[transactionId].data); require(success, "Transaction execution failed"); ... } function isValidFunctionSelector(TransactionType _type, bytes4 selector) private pure returns (bool) { if (_type == TransactionType.AddOwner && selector == this.setOwner.selector) return true; if (_type == TransactionType.RemoveOwner && selector == this.setOwner.selector) return true; if (_type == TransactionType.SetParameter && selector == this.setParameters.selector) return true; if (_type == TransactionType.SetSalesAddress && selector == this.setSalesAddress.selector) return true; return false; } ```

LowPotential Unintended Max Pending TimeOpen

The `timestamp` of a transaction is updated each time a valid owner signs the transaction. This is observed in functions like `signTransaction`, `signUserAdd`, and `signUserDel` where after a signature, the transaction's timestamp is set to the current block time (`transaction.timestamp = block.timestamp`). Our understanding is that if a transaction does not accumulate enough signatures (reaching the `MIN_SIGNATURES` threshold) within 96 hours, it should be considered expired and deleted, making room for a new transaction. However, given the timestamp refresh mechanism on each signature, the real pending time (the time a transaction remains in a pending state without reaching the minimum required signatures) can indeed exceed the intended 96 hours. This pending time would reset with each new signature, potentially leading to scenarios where a transaction remains pending for considerably longer than intended, especially if signatures are spaced out over time.

Show 4 more
LowPotential Unintended Reward Loss During Unstake OOpen

The `unStake()` function allows users to withdraw their staked principal and rewards, but only the unstaked portion (`_tokenAmount`) will be considered for reward calculation. The remaining tokens (`stake.tokenAmount - _tokenAmount`) will not accrue rewards, and the staking duration will reset. Users must wait for the full duration again before they can withdraw the rest. ```solidity /** * @dev unstake a token by the staker. * @param _tokenAmount The trade to be cancelled. */ function unStake(address _itemToken,uint256 _tokenAmount,uint256 _duration) public nonReentrant //anti re-entranc //whenNotPaused notContract(msg.sender) //anti contract address virtual { ... require( stake.tokenAmount >=_tokenAmount, "Stake token Amount insufficient." ); ... uint256 stakeProfit=0; uint256 payprofit = 0; if(_duration == 180) payprofit = _tokenAmount*half_APY/100/2; //half of year should divided by 2 if(_duration == 365) payprofit = _tokenAmount*full_APY/100; // full year if(_duration == 730) payprofit = _tokenAmount*two_APY*2/100; // two years should multiply by 2 ... stake.tokenAmount -= _tokenAmount; stake.reward = 0; stake.timestamp = block.timestamp; totalStakeAmount[_itemToken]-=_tokenAmount; stakeAmounts[_itemToken][_duration]-=_tokenAmount; ... } ```

LowTransaction Ready to Be Executed Can Be Deleted BOpen

The method `deleteMaxPendingTx` can indeed delete a transaction that is ready for execution if a new transaction is proposed. ```solidity //for set param Txs if (pendingsetTx.timestamp > 0 ){ if (block.timestamp - pendingsetTx.timestamp > maxPendingTime){ deleteSetTx(txId); }else revert("has pending txs yet! Please sign them first"); } ``` Suppose a transaction reaches the necessary threshold of approvals and is pending execution, having passed any mandatory waiting periods (`observationPeriod`). After 96 hours, if the transaction is not executed by any owner and a new transaction is initiated, the `deleteMaxPendingTx` function will be called and then the transaction ready to be executed will be deleted.

LowUnused `duration` levelOpen

Upon reviewing the `getUserAllStake()` function, we observed that it allows users to query seven different duration levels, and users can specify any duration when staking and unstaking. However, only three duration levels—180, 365, and 730—entitle users to claim rewards.

LowUnused `timestamp` In `buyNode` FunctionOpen

In the `buyNode` function, the `timestamp` parameter indeed appears to be unused in any meaningful validation, which might be a security oversight depending on the intended functionality. Typically, a `timestamp` is used to ensure that the action being taken is relevant to a specific window of time. ```solidity function buyNode( uint256 timestamp, bytes32 nonce, bytes32 message, bytes memory signature, address parentAddress, uint256 maxPrice ) nonReentrant notContract external returns (uint256){ require(!halted, "Node Halted!"); bytes32 challenge = getChallenge(timestamp, nonce, parentAddress, msg.sender); ... } ```

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (21)

  • https://github.com/mumuteam-source/metacontract/MCTP%20vNode.sol at commit dac85fadd41a3f9bea7cde1cde481eba8d358e0b
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit 3e65a02cd88cc64690e787e18886a44af75f1e9e
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit bfd603e00087827439369e60033c2d2af30da718
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit f16d09b85a835c8d9f619d727b3921bf44d85909
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit fdedfc84355cc5908fb3b0ebc0e148885b939f27
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit fbdb0060f9a807c710222d47e226e7c7dea8c150
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 3d630b3ba3f6c31734859c86a97e222e52489c52
  • https://github.com/mumuteam-source/metacontract/MCTP%20Stake%20NEW.sol at commit dac85fadd41a3f9bea7cde1cde481eba8d358e0b
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit 9b124a756bad40ca90b34042fc96d883c5c88764
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit 5e765e5ca1403b0ef55b8bdf736585d538ca0f55
  • https://github.com/metacraftpro/metacontract/MCTP%20vNode.sol at commit c30afe4c6c3b9884a92be14d69c68eae0fe6195d
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 640589c2ee616fa2f11228d5a41bb07ace33664e
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 0cc6098dcc40e32620009964bb7d85fd0a38d35c
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 124e3b2068e94e6f5615cf71f0e84385a59d6247
  • https://github.com/mumuteam-source/metacontract/MCTP%20Stake%20NEW.sol at commit 59a15b78b6e84209e27d74f40d7b14e62d7cd42c
  • https://github.com/mumuteam-source/metacontract/MCTP%20vNode.sol at commit 48a93de8e9ae248aae799f2b782b6eb00e7acf80
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 76a4e15100c639eebb31c961d5f9730c4af40cc6
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit e399232416a9bfc7f47dbfb6c96e3e691922f73e
  • https://github.com/metacraftpro/metacontract/MCTP%20Stake%20NEW.sol at commit 67d8592a0ae3f978a2c9b74fb4b49ce42a415eec
  • https://github.com/mumuteam-source/metacontract/MCTP%20vNode.sol at commit 59a15b78b6e84209e27d74f40d7b14e62d7cd42c
  • https://github.com/mumuteam-source/metacontract/MCTP%20Stake%20NEW.sol at commit 48a93de8e9ae248aae799f2b782b6eb00e7acf80

Part of MUMUBIT TOKEN’s security record

MUMUBIT TOKEN
Audits4 audits by 1 firmLast audit Mar 2025, by CertiK.
Critical findingsNo open critical findingsAcross the 4 reports, 5 critical findings were reported. Each is marked fixed by the firm that reported it.
Open MUMUBIT TOKEN’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