CertiK reviewed MUMUBIT TOKEN (Metacraft.) on 12 Apr 2024: 21 findings, 0 critical; 14 marked fixed, 7 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 1 |
| Medium | 2 | 0 | 2 |
| Low | 4 | 0 | 11 |
In the `withdrawAllFunds()` function, there's a critical flaw where the total invested amount (`investedAmount[tokenAddress_]`) is reduced by the current balance of the token within the contract (`IERC20(tokenAddress_).balanceOf(address(this))`). This operation assumes that the contract's token balance only increases through legitimate investment actions via `investInWithVerify()`. However, an attacker could artificially increase the contract's token balance by directly transferring tokens to it. If such an action occurs, the subtraction operation could lead to an underflow in `investedAmount[tokenAddress_]`, potentially causing legitimate invested tokens to become permanently locked in the contract due to an inaccurate representation of available funds. ```solidity=702 function withdrawAllFunds(address tokenAddress_) external nonReentrant notContract(msg.sender) withdrawAddressOnly() { uint256 balance = IERC20(tokenAddress_).balanceOf(address(this)); require(investCoinAddress == tokenAddress_ || projectCoinAddress == tokenAddress_, "Coin Address Not Correct!"); require(block.timestamp - claimTimestamp >= 3 days, "Withdraw Fund Until 48 hours after Claim "); if(tokenAddress_ == investCoinAddress){ investedAmount[tokenAddress_] -= balance; refundedAmount[tokenAddress_] += balance; emit IDOStatusChange(tokenAddress_, balance, msg.sender, "Manager Withdraw invest Coins"); } else if(tokenAddress_ == projectCoinAddress){ projectCoinAmount[tokenAddress_] = IERC20(tokenAddress_).balanceOf(address(this)); claimedAmount[tokenAddress_] += balance; emit IDOStatusChange(tokenAddress_, balance, msg.sender, "Manager Withdraw invest Coins"); } else revert("withDraw TokenAddress ERROR"); IERC20(tokenAddress_).safeTransfer(msg.sender, balance); } ```
The function `removeOwner()` can be called by anyone as it has no access restriction. This enables anyone to call this and remove any multisig `_owners`.
The `deleteMaxPendingTx()` function within the contract utilizes a time lock mechanism, `maxPendingTime`, intended to restrict the deletion of pending proposals until a certain period has passed. However, a valid owner can bypass this time lock by directly invoking `deleteTransaction()` to delete pending proposals.
In the `withdrawAllFunds()` function, there's an inconsistency between the implemented logic and the comment. The code enforces a condition that restricts fund withdrawal until 72 hours (3 days) have passed since the `claimTimestamp`. However, the comment misleadingly states a 48-hour (2 days) wait period. ```solidity=709 require(block.timestamp - claimTimestamp >= 3 days, "Withdraw Fund Until 48 hours after Claim "); ```
The `investInWithVerify()` function is designed to accept `userLevel` values of only 1, 2, and 3, aligning with specified access or participation levels. Despite this restriction, there are instances within the contract where `userLevel` 4 is used.
The following comment is outdated and incorrect since the contract logic has been changed. ```solidity=393 require(block.timestamp - transaction.timestamp >= 24 hours,"Time Lockin for 48 Hours for at Least 2 signers !"); ```
The `deleteMaxPendingTx()` function within the contract utilizes a time lock mechanism, `maxPendingTime`, intended to restrict the deletion of pending proposals until a certain period has passed. Currently, this time lock is set to 96 seconds, a duration significantly shorter than the intended design of 96 hours. This discrepancy could lead to premature deletion of pending proposals, potentially undermining the governance process or other sequential actions reliant on the time lock mechanism.
In the `withdrawAllFunds()` function, when the `tokenAddress_` parameter matches the `projectCoinAddress`, the `projectCoinAmount[projectCoinAddress]` captures the token balance before the token transfer, contrary to the intended behavior of recording the current token balance.
The implementation of the `notContract` check can not cover all scenarios. The check can be bypassed if the call is from the constructor of a smart contract or when the contract is destroyed. In that case, the codesize will also be zero. ```solidity modifier notContract(address _address) { uint256 codeSize; assembly { codeSize := extcodesize(_address) } require(codeSize == 0, "Contracts are not allowed to receive tokens"); _; } ```
`ecrecover()` accepts as valid, two versions of signatures, meaning an attacker can use the same signature twice, or an attacker may be able to front-run the original signer with the altered version of the signature, causing the signer's transaction to revert due to nonce reuse.
A cross-chain replay attack is a type of malicious activity that occurs when a transaction or action intended for one blockchain is duplicated and executed on another blockchain, typically with unintended consequences or malicious intent. The contract is vulnerable to such attacks as it fails to validate signatures against the current chain ID, allowing attackers to exploit this on different chains. Additionally, when there is a major change in the blockchain, known as a "hard fork", it can split into two chains with different IDs. Using hardcoded or cached chain ID values can be risky because if the chain ID changes after the split, the signature that uses the previous chain ID will not work.
The `investInWithVerify()`, `refundOut()`, and `claimProjectCoin()` functions iterate over the `investors` array, which contains all investor information. Given that the array length can be increased by any address possessing the correct signature, these functions are exposed to potential denial-of-service (DoS) attacks. Malicious actors could excessively enlarge the `investors` array, leading to excessive gas costs or out-of-gas errors during function execution, thereby hindering contract operability.
The `setProjectInfo()` function allows modification of crucial variables before `startTimestamp`. The use of `publicKey`'s signature as a means of authentication allows any `msg.sender` with the correct signature to invoke this function. Given that other functions like `investInWithVerify()`, `refundOut()`, and `claimProjectCoin()` are restricted post-`startTimestamp` and considering normal users can obtain signatures, there's ambiguity around who can securely obtain and use the signature pre-`startTimestamp`. This raises security concerns about unauthorized alterations to project settings by entities other than the project owner.
The `withdrawAllFunds()` function allows the `withdrawAddress` to withdraw the project coins from the contract. However, there's a risk that investors might not be able to claim their tokens if the `withdrawAddress` withdraws all project coins from the contract.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (3)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething 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