MCCNodes - Smart Contract Audit Report

Audit Summary

MCC Staking Audit Report Multi-Chain Capital is building a sale based NFT project with dividend earning properties.

For this audit, we reviewed Multi-Chain Capital's MCCNode, MCCNodeRewards, and DexUtils contracts at commit 3cca22edabf066940e5261ce3b039ef63a2ddad4 on the team's private GitHub repository.

We previously reviewed the project team's MCC Staking platform here.

Audit Findings

The reward mechanism is susceptible to manipulation by flash loans allowing an attacker to steal the reward tokens.
Please ensure trust in the team prior to investing as they have substantial control in the ecosystem.
Date: February 10th, 2022.
Updated: February 11th, 2022 to address flash loan risk mitigation by the team.

Finding #1 - DexUtils - Medium (Partially Resolved)

Description: In the getMainPriceViaNativePair() function, the reward token's value is calculated based on two Uniswap liquidity pools.
Risk/Impact: If another $MCC liquidity pool is created, an attacker could use that secondary pool and flash loans to manipulate the ratio of the original $MCC token to native token liquidity pair, thereby greatly influencing the determined value of the tokens in the contract. The new reward tokens' value would then be used to calculate how many reward tokens to distribute, leading to an attacker receiving an inflated amount of reward tokens. A whale that amasses enough $MCC tokens could also potentially perform the same manipulation.
Exploit Scenario:
  1. User A borrows $MCC tokens from an alternative $MCC token liquidity pool.
  2. User A then buys all the $WETH in the original $MCC and $WETH liquidity pool incurring an 8% fee.
  3. User A mints themselves MultiNode NFTs, which are given the inflated rewards rate.
  4. User A sells the ETH back to $MCC and $WETH liquidity pool to regain the $MCC tokens they borrowed.
  5. User A then returns the $MCC back to the alternative pool incurring another 8% fee.
  6. User A claims the inflated dividend reward on their MultiNode NFTs.

Recommendation: The team should implement a Time Weighted Average Price (TWAP) or utilize Chainlink to mitigate potential price manipulations.
Update: Currently, there aren't any additional $MCC liquidity pools or top holders with enough value to pose as potential manipulation risks. The team has implemented a Chainlink price feed for the native and stable token liquidity pool and is also in the process of integrating a Chainlink price feed for the $MCC token. The team also reserves the right to update any token's reward rate at any time.

Finding #2 - MCCNode - Informational

Description: Several functions are declared public, but are never called internally.
mint, contractURI, pause, unpause
Recommendation: We recommend declaring these functions external for additional gas savings on each call.

Finding #3 - MCCNode - Informational

Description: The mint() function is declared as payable but should not be receiving ETH.
Recommendation: We recommend removing the payable declaration on the mint() function and ETH returning functionality within the function for gas savings.

Finding #4 - MCCNode - Informational

Description: The contract contains an event, variables, and functions that are not used.
Functions: setRoyaltyAddress(), setRoyaltyBasisPoints(), pause(), unpause()
Variables: royaltyAddress, royaltyBasisPoints
Events: RoyaltyBasisPoints
Recommendation: If not needed for future functionality we recommend removing the event, variables, and functions listed above to reduce contract size and save on deployment costs.

Contracts Overview

  • These contracts are developed with Solidity 0.8.x, so they are protected from overflow/underflow attacks.
  • The team must exercise caution when setting the reward token and must avoid using an ERC777-compliant token.
MCCNode Contract:
  • This contract is used to facilitate the sale of MultiNode NFTs.
  • While the sale is active, users can purchase any amount of NFTs using specified amounts of "main" tokens and "stable" tokens defined by the team.
  • Upon purchasing NFTs users specify a tier ID from 1 to 5 that corresponds to different names, prices, and reward rates.
  • Both purchasing tokens are transferred from the user to the payment address controlled by the team.
  • The "main" token value is dependent on a "main" and "native" token Uniswap liquidity pair as well as a Chainlink price feed for the "native" and "stable" token pair. By using these two pricing mechanisms, the value of the "main" token used for rewards is determined.
  • As a result, the reward rate of an NFT may stay the same, but the number of reward tokens calculated to satisfy that rate can change because of a change in either pairs' ratio.

  • The owner can start or pause the sale at any time.
  • The owner can set the sale start time at any time.
  • The owner can pause/unpause the contract at any time.
  • The owner can update the token reward rate at any time.
  • The owner can purchase MultiNode NFTs at any time, whether the sale is active or not.
  • The owner can set the payment address at any time.
  • The owner can set the main and stable token addresses at any time.
  • The owner can set the royalty address at any time.
  • The owner can set the royalty basis points to any value at any time.
  • The owner can set the base token URI value at any time.
  • The owner can add a new tier at any time.
  • The owner can update any tier's properties at any time.
  • The owner can set the reward rate of any token to any value at any time.
MCCNodeRewards Contract:
  • This contract enables users to claim rewards based on their purchased MultiNode NFTs.
  • MultiNode NFT rewards are calculated using the NFT's reward rate and the reward frequency which is set to daily by default.
  • Rewards are calculated and paid using the "main" token defined within the MCCNode contract.
  • The owner can withdraw any tokens or ETH stored in the contract at any time.
  • The owner can set the reward frequency to any duration at any time.

External Threat Results

Vulnerability CategoryNotesResult
Arbitrary Storage WriteN/APASS
Arbitrary JumpN/APASS
Centralization of Control
  • The team can update the main and stable token addresses at any time.
  • The team can withdraw the tokens from the contract at any time.
  • The team can change the reward rate of any tier at any time.
  • The team can update the per day return of any NFT, to any value, at any time.
  • WARNING
    Delegate Call to Untrusted ContractN/APASS
    Dependence on Predictable VariablesN/APASS
    Deprecated OpcodesN/APASS
    Ether ThiefN/APASS
    ExceptionsN/APASS
    External CallsN/APASS
    Flash LoansIf additional MCC liquidity pools are created, they could be used to manipulate the NFT reward rate.WARNING
    Integer Over/UnderflowN/APASS
    Logical IssuesN/APASS
    Multiple SendsN/APASS
    OraclesN/APASS
    SuicideN/APASS
    State Change External CallsN/APASS
    Unchecked RetvalN/APASS
    User Supplied AssertionN/APASS
    Critical Solidity CompilerN/APASS
    Overall Contract Safety PASS

    MCCNode Contract

     Token Graph

    Multi-file Token

    		
    ($) = payable function
     # = non-constant function		
    			
    + [Int] IERC20 
        - [Ext] totalSupply
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Int] IERC165 
        - [Ext] supportsInterface
    
     + [Int] IERC721 (IERC165)
        - [Ext] balanceOf
        - [Ext] ownerOf
        - [Ext] safeTransferFrom #
        - [Ext] transferFrom #
        - [Ext] approve #
        - [Ext] getApproved
        - [Ext] setApprovalForAll #
        - [Ext] isApprovedForAll
        - [Ext] safeTransferFrom #
    
     + [Int] IERC721Receiver 
        - [Ext] onERC721Received #
    
     + [Int] IERC721Metadata (IERC721)
        - [Ext] name
        - [Ext] symbol
        - [Ext] tokenURI
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Int] functionStaticCall
        - [Int] functionStaticCall
        - [Int] functionDelegateCall #
        - [Int] functionDelegateCall #
        - [Int] verifyCallResult
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     + [Lib] Strings 
        - [Int] toString
        - [Int] toHexString
        - [Int] toHexString
    
     +  ERC165 (IERC165)
        - [Pub] supportsInterface
    
     +  ERC721 (Context, ERC165, IERC721, IERC721Metadata)
        - [Pub]  #
        - [Pub] supportsInterface
        - [Pub] balanceOf
        - [Pub] ownerOf
        - [Pub] name
        - [Pub] symbol
        - [Pub] tokenURI
        - [Int] _baseURI
        - [Pub] approve #
        - [Pub] getApproved
        - [Pub] setApprovalForAll #
        - [Pub] isApprovedForAll
        - [Pub] transferFrom #
        - [Pub] safeTransferFrom #
        - [Pub] safeTransferFrom #
        - [Int] _safeTransfer #
        - [Int] _exists
        - [Int] _isApprovedOrOwner
        - [Int] _safeMint #
        - [Int] _safeMint #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _transfer #
        - [Int] _approve #
        - [Int] _setApprovalForAll #
        - [Prv] _checkOnERC721Received #
        - [Int] _beforeTokenTransfer #
    
     +  ERC721Burnable (Context, ERC721)
        - [Pub] burn #
    
     + [Int] IERC721Enumerable (IERC721)
        - [Ext] totalSupply
        - [Ext] tokenOfOwnerByIndex
        - [Ext] tokenByIndex
    
     +  ERC721Enumerable (ERC721, IERC721Enumerable)
        - [Pub] supportsInterface
        - [Pub] tokenOfOwnerByIndex
        - [Pub] totalSupply
        - [Pub] tokenByIndex
        - [Int] _beforeTokenTransfer #
        - [Prv] _addTokenToOwnerEnumeration #
        - [Prv] _addTokenToAllTokensEnumeration #
        - [Prv] _removeTokenFromOwnerEnumeration #
        - [Prv] _removeTokenFromAllTokensEnumeration #
    
     +  Pausable (Context)
        - [Pub]  #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  ERC721Pausable (ERC721, Pausable)
        - [Int] _beforeTokenTransfer #
    
     +  Ownable (Context)
        - [Pub]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     + [Lib] Counters 
        - [Int] current
        - [Int] increment #
        - [Int] decrement #
        - [Int] reset #
    
     + [Lib] SafeMath 
        - [Int] tryAdd
        - [Int] trySub
        - [Int] tryMul
        - [Int] tryDiv
        - [Int] tryMod
        - [Int] add
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] mod
        - [Int] sub
        - [Int] div
        - [Int] mod
    
     + [Int] IMCCNode (IERC721)
        - [Ext] mainToken
        - [Ext] stableToken
        - [Ext] tokenMintedAt
        - [Ext] tokenLastTransferredAt
        - [Ext] tokenPerDayReturn
        - [Ext] mint ($)
    
     + [Int] IDexUtils 
        - [Ext] getMainPriceViaNativePair
    
     + [Int] IERC20Decimals (IERC20)
        - [Ext] decimals
    
     +  MCCNode (IMCCNode, Ownable, ERC721Burnable, ERC721Enumerable, ERC721Pausable)
        - [Pub]  #
           - modifiers: ERC721
        - [Ext] getDexUtils
        - [Ext] startPublicSale #
           - modifiers: onlyOwner,whenPublicSaleNotActive
        - [Ext] setPublicSaleStartTime #
           - modifiers: onlyOwner
        - [Ext] togglePublicSaleActive #
           - modifiers: onlyOwner
        - [Ext] pausePublicSale #
           - modifiers: onlyOwner,whenPublicSaleActive
        - [Ext] getAllTiers
        - [Ext] royaltyInfo
        - [Pub] getElapsedSaleTime
        - [Pub] mint ($)
           - modifiers: whenOwnerOrPublicSaleActive
        - [Int] payToMint #
           - modifiers: whenOwnerOrPublicSaleActive
        - [Ext] setPaymentAddress #
           - modifiers: onlyOwner
        - [Ext] setRoyaltyAddress #
           - modifiers: onlyOwner
        - [Ext] setMainToken #
           - modifiers: onlyOwner
        - [Ext] setStableToken #
           - modifiers: onlyOwner
        - [Ext] setRoyaltyBasisPoints #
           - modifiers: onlyOwner
        - [Ext] setBaseURI #
           - modifiers: onlyOwner
        - [Ext] addOrUpdateTier #
           - modifiers: onlyOwner
        - [Ext] updateNodePerDayReturn #
           - modifiers: onlyOwner
        - [Pub] getPerDayReturnFromTier
        - [Pub] tokenURI
        - [Pub] contractURI
        - [Pub] supportsInterface
        - [Pub] pause #
           - modifiers: onlyOwner
        - [Pub] unpause #
           - modifiers: onlyOwner
        - [Int] _baseURI
        - [Int] _beforeTokenTransfer #

    MCCNodeRewards Contract

    BEP20 Token Graph

    Multi-file Token

    												
    ($) = payable function
     # = non-constant function
     
     + [Int] IERC20 
        - [Ext] totalSupply
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Pub]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     + [Lib] SafeMath 
        - [Int] tryAdd
        - [Int] trySub
        - [Int] tryMul
        - [Int] tryDiv
        - [Int] tryMod
        - [Int] add
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] mod
        - [Int] sub
        - [Int] div
        - [Int] mod
    
     + [Int] IERC165 
        - [Ext] supportsInterface
    
     + [Int] IERC721 (IERC165)
        - [Ext] balanceOf
        - [Ext] ownerOf
        - [Ext] safeTransferFrom #
        - [Ext] transferFrom #
        - [Ext] approve #
        - [Ext] getApproved
        - [Ext] setApprovalForAll #
        - [Ext] isApprovedForAll
        - [Ext] safeTransferFrom #
    
     + [Int] IMCCNode (IERC721)
        - [Ext] mainToken
        - [Ext] stableToken
        - [Ext] tokenMintedAt
        - [Ext] tokenLastTransferredAt
        - [Ext] tokenPerDayReturn
        - [Ext] mint ($)
    
     +  MCCNodeRewards (Ownable)
        - [Pub]  #
        - [Ext] shareholderToken
        - [Ext] dividendToken
        - [Pub] claimDividend #
        - [Ext] claimDividendsMulti #
        - [Pub] getUnpaidEarnings
        - [Ext] getTotalEarnings
        - [Int] _getTotalNumberClaims
        - [Ext] setRewardFrequencySeconds #
           - modifiers: onlyOwner
        - [Ext] withdrawTokens #
           - modifiers: onlyOwner
        - [Ext] withdrawETH #
           - modifiers: onlyOwner
       

    DexUtils Contract

    BEP20 Token Graph

    Multi-file Token

    												
    ($) = payable function
     # = non-constant function
     
     + [Int] IERC20 
        - [Ext] totalSupply
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] SafeMath 
        - [Int] tryAdd
        - [Int] trySub
        - [Int] tryMul
        - [Int] tryDiv
        - [Int] tryMod
        - [Int] add
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] mod
        - [Int] sub
        - [Int] div
        - [Int] mod
    
     + [Int] IUniswapV2Factory 
        - [Ext] feeTo
        - [Ext] feeToSetter
        - [Ext] getPair
        - [Ext] allPairs
        - [Ext] allPairsLength
        - [Ext] createPair #
        - [Ext] setFeeTo #
        - [Ext] setFeeToSetter #
    
     + [Int] IUniswapV2Pair 
        - [Ext] name
        - [Ext] symbol
        - [Ext] decimals
        - [Ext] totalSupply
        - [Ext] balanceOf
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transfer #
        - [Ext] transferFrom #
        - [Ext] DOMAIN_SEPARATOR
        - [Ext] PERMIT_TYPEHASH
        - [Ext] nonces
        - [Ext] permit #
        - [Ext] MINIMUM_LIQUIDITY
        - [Ext] factory
        - [Ext] token0
        - [Ext] token1
        - [Ext] getReserves
        - [Ext] price0CumulativeLast
        - [Ext] price1CumulativeLast
        - [Ext] kLast
        - [Ext] mint #
        - [Ext] burn #
        - [Ext] swap #
        - [Ext] skim #
        - [Ext] sync #
        - [Ext] initialize #
    
     + [Int] IUniswapV2Router01 
        - [Ext] factory
        - [Ext] WETH
        - [Ext] addLiquidity #
        - [Ext] addLiquidityETH ($)
        - [Ext] removeLiquidity #
        - [Ext] removeLiquidityETH #
        - [Ext] removeLiquidityWithPermit #
        - [Ext] removeLiquidityETHWithPermit #
        - [Ext] swapExactTokensForTokens #
        - [Ext] swapTokensForExactTokens #
        - [Ext] swapExactETHForTokens ($)
        - [Ext] swapTokensForExactETH #
        - [Ext] swapExactTokensForETH #
        - [Ext] swapETHForExactTokens ($)
        - [Ext] quote
        - [Ext] getAmountOut
        - [Ext] getAmountIn
        - [Ext] getAmountsOut
        - [Ext] getAmountsIn
    
     + [Int] IUniswapV2Router02 (IUniswapV2Router01)
        - [Ext] removeLiquidityETHSupportingFeeOnTransferTokens #
        - [Ext] removeLiquidityETHWithPermitSupportingFeeOnTransferTokens #
        - [Ext] swapExactTokensForTokensSupportingFeeOnTransferTokens #
        - [Ext] swapExactETHForTokensSupportingFeeOnTransferTokens ($)
        - [Ext] swapExactTokensForETHSupportingFeeOnTransferTokens #
    
     + [Int] IERC20Decimals (IERC20)
        - [Ext] decimals
    
     +  DexUtils 
        - [Pub]  #
        - [Ext] getMainPriceViaNativePair
        - [Prv] _getTokenPrice