
MCCNodes - Smart Contract Audit Report
Audit Summary
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:
- User A borrows $MCC tokens from an alternative $MCC token liquidity pool.
- User A then buys all the $WETH in the original $MCC and $WETH liquidity pool incurring an 8% fee.
- User A mints themselves MultiNode NFTs, which are given the inflated rewards rate.
- User A sells the ETH back to $MCC and $WETH liquidity pool to regain the $MCC tokens they borrowed.
- User A then returns the $MCC back to the alternative pool incurring another 8% fee.
- 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.Recommendation: We recommend declaring these functions external for additional gas savings on each call.mint, contractURI, pause, unpauseFinding #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.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.Functions: setRoyaltyAddress(), setRoyaltyBasisPoints(), pause(), unpause() Variables: royaltyAddress, royaltyBasisPoints Events: RoyaltyBasisPoints
Contracts Overview
MCCNode Contract:
- 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.
MCCNodeRewards 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.
- 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 Category | Notes | Result |
|---|---|---|
| Arbitrary Storage Write | N/A | PASS |
| Arbitrary Jump | N/A | PASS | Centralization of Control | WARNING |
| Delegate Call to Untrusted Contract | N/A | PASS |
| Dependence on Predictable Variables | N/A | PASS |
| Deprecated Opcodes | N/A | PASS |
| Ether Thief | N/A | PASS |
| Exceptions | N/A | PASS |
| External Calls | N/A | PASS |
| Flash Loans | If additional MCC liquidity pools are created, they could be used to manipulate the NFT reward rate. | WARNING |
| Integer Over/Underflow | N/A | PASS |
| Logical Issues | N/A | PASS |
| Multiple Sends | N/A | PASS |
| Oracles | N/A | PASS |
| Suicide | N/A | PASS |
| State Change External Calls | N/A | PASS |
| Unchecked Retval | N/A | PASS |
| User Supplied Assertion | N/A | PASS |
| Critical Solidity Compiler | N/A | PASS |
| Overall Contract Safety | PASS |
MCCNode Contract


($) = 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


($) = 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


($) = 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