MoonForce
Smart Contract Audit Report
Audit Summary
MoonForce is building a new Presale contract generator and an LP token locking mechanism.
For this audit, we reviewed the following contracts on the Binance Smart Chain Mainnet:
- MoonForceLocker contract at 0x412397FeC775d60a8F2489E67692fa3bF32d4807.
- PresaleSettings contract at 0x342dcC1cD4e8822297b3B7c53C1864Ecbd988ea8.
- PresaleFactory contract at 0xF710DD6BD3414Cd4c569Ba0EDDFA8d3585EBaa9e.
- PresaleLockForwarder contract at 0x01bD79A5486091BEe45aDA96AEC93AaCE6A44Fb7.
- PresaleGenerator contract at 0x20e21425dFd0dA0F6810542Cdb3127baa9aD9B5e.
Audit Findings
Please ensure trust in the team prior to investing as they have substantial control in the ecosystem.
Date: April 21st, 2022.Finding #1 - MoonForceLocker - High
Description: A redundant safeApprove() call is performed before transferring the referral fee tokens to the referral address.Risk/Impact: The referral address will be able to spend tokens on behalf of the secondaryFeeToken address for the referralFee value.TransferHelper.safeApprove(address(gFees.secondaryFeeToken), _referral, referralFee); TransferHelper.safeTransfer(address(gFees.secondaryFeeToken), _referral, referralFee);
Recommendation: The above safeApprove() call should be removed from the contract.
Resolution: The team has not yet addressed this issue.Finding #2 - Presale - Low
Description: The safeTransferBaseToken() function uses thetransfer()
function instead of thecall()
function to transfer BNB to users.
Risk/Impact: Thetransfer()
function uses a hardcoded gas amount of 2300, meaning transactions could run out of gas as users receive BNB.
Recommendation: Use.call.value{...}("")
instead as it does not have a gas limitation.
Resolution: The team has not yet addressed this issue.
Contracts Overview
MoonForceLocker Contract:
- The contracts utilize ReentrancyGuard to prevent against reentrancy attacks in applicable functions.
- The contracts utilize the SafeMath library to protect from any possible overflows/underflows.
Presale Contracts:
- Any user may use this contract to lock a valid Pancakeswap V2 LP token. Tokens may not be withdrawn until a specified time has passed.
- Users specify a lock "owner" which is the recipient for the locked LP Tokens.
- Users must pay either a BNB fee or a secondary fee in the form of the secondary token set by the team.
- If the fee is in BNB, the collected BNB is sent to the team's Dev address.
- If the fee is in the secondary token, the collected tokens are burned in the token contract.
- If a referral address was passed in and the referral token is set, the balance of the referral address must be greater than or equal to the minimum referral hold value set by the team.
- Users who enter a valid Referral address are eligible for a fee discount.
- The BNB or tokens collected through the Referral fee are subtracted from the above BNB or secondary fee and sent to the Referral address.
- An additional Liquidity fee is charged when locking tokens that is allocated to the team's Dev address.
- Whitelisted users are exempt from the fees when locking LP tokens.
- Once the specified unlock time has passed, the lock owner may withdraw the deposited tokens at any time.
- Lock owners may extend the lock duration of any of their locks. An additional Liquidity fee is taken when extending the lock duration.
- Lock owners may add additional LP tokens to any of the locks that they own. An additional Liquidity fee is taken from the original balance before the new LP tokens are added.
- Token locks can also be split into two separate locks. There is a fee in BNB associated with this action.
- Lock owners may transfer the ownership of any owned locks to any other address.
- If a Migrator address has been set, owners may use the Migrator to move their locks to a new contract. The Migrator contract was outside the scope of this audit, so we cannot provide an assessment with regards to security.
- The owner can update the Dev and Migrator address at any time.
- The owner can set the minimum amount of the secondary token a referral address must hold to any value at any time.
- The owner can set the secondary token to any address at any time.
- The owner can set the BNB fee, Liquidity fee, Referral fee, Referral discount, Secondary token fee, and Secondary token discount to any values at any time.
- The owner can add or remove any address from the fee whitelist at any time.
- Any user may use the PresaleGenerator contract to generate a new Presale contract where users will be able to deposit BNB or the specified Base token into the contract in exchange for an amount of the Presale token.
- In order for a new contract to be generated, the Presale generator contract must be pre-approved by the owner as a valid generator address in the PresaleFactory contract.
- Every generated Presale contract will be registered as a valid contract in the PresaleFactory contract.
- The team will set the addresses of the Presale token and the Base token accepted for payment upon deployment of the contract.
- Upon deployment, the team will also set the soft cap and hard cap amounts, the price of the Presale token, a referral address, the maximum amount that can be purchased per wallet, the percentage of tokens that will be locked as liquidity, and the start time & end of the sale.
- The total Presale time cannot exceed the maximum value set in the PresaleSettings contract.
- The provided liquidity percentage must be at least 30% and the lock duration cannot be less than 4 weeks.
- The amount of BNB entered when creating a Presale contract must be equivalent to the Creation fee in the PresaleSettings contract.
- The BNB collected through the Creation fee is sent to the team's Fee address.
- The provided referral address must have been whitelisted by the team in the PresaleSettings contract.
- Users can deposit an amount of BNB (or the base token) into the contract and will be allocated a number of the Presale token based on the amount that the user deposited.
- If the current block falls within Round 1 (determined by the owner), the user must hold the minimum number of the early-access token set by the team in the PresaleSettings contract in order to deposit BNB or tokens.
- After the Round 1 block has passed, the early-access token restriction is no longer enforced.
- A Presale is considered successful when the total number of base tokens in the contract exceeds the hard cap or if the end block has been reached and the soft cap has been met.
- After the presale has been considered successful, the Presale owner must then add liquidity.
- Upon adding liquidity, a pair will be created with the Presale token and the Base token and liquidity will be locked in the MoonforceLocker contract for the duration set by the Presale owner via the PresaleLockForwarder contract.
- A Referral fee and a MoonForce fee are charged on both the Base tokens and Presale tokens before adding liquidity. The collected tokens are sent to the Referral address and the team's Base fee address respectively.
- If the remaining Presale tokens in the contract is greater than the total number of Presale tokens sold, the remaining tokens in the contract will be sent to the 0x..dead address.
- Any remaining Base tokens or BNB in the contract are sent to the Presale owner.
- Users can withdraw the full number of Presale tokens due to them after liquidity has been added by the team.
- A presale is considered unsuccessful if the end time has been reached and the soft cap has not been met.
- Any user can force fail the presale if the liquidity pair already exists but liquidity had yet to be added by the Presale owner.
- The team's Fee address can force fail the presale at any time.
- If a presale has failed, the user can withdraw their deposited BNB or Base tokens at any time, and the Presale owner can withdraw all of the presale tokens at any time.
- The Presale owner can update the maximum spend amount to any value at any time.
- The Presale owner can update the start time and end time if the presale has not yet started. The total duration cannot exceed the maximum Presale length in the PresaleSettings contract.
- The Presale owner can enable/disable the enforcing of the presale whitelist at any time.
- The Presale owner can add/removes accounts from the presale whitelist at any time.
- The owner can update the team's Fee wallets at any time.
- The owner can update the Base fee, Presale token fee, Referral fee, and Creation fee to any values at any time.
- The owner can set the length of Round 1 to any value at any time.
- The owner can set the maximum Presale length to any value at any time.
- The owner can add/remove accounts from the referral list at any time.
- The owner can add/remove early-access token addresses at any time.
Audit Results
Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Jump/Storage Write | N/A | PASS |
Centralization of Control | The owner can update the Base fee, Presale token fee, Referral fee, and Creation fee to any values at any time. | WARNING |
Compiler Issues | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Ether/Token Theft | N/A | PASS |
Flash Loans | N/A | PASS |
Front Running | N/A | PASS |
Improper Events | N/A | PASS |
Improper Authorization Scheme | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues | N/A | PASS |
Oracle Issues | N/A | PASS |
Outdated Compiler Version | N/A | PASS |
Race Conditions | N/A | PASS |
Reentrancy | N/A | PASS |
Signature Issues | N/A | PASS |
Unbounded Loops | N/A | PASS |
Unused Code | N/A | PASS |
Overall Contract Safety | PASS |
MoonForceLocker Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IMigrator
- [Ext] migrate #
+ [Int] IMoonForceSwapFactory
- [Ext] feeTo
- [Ext] feeToSetter
- [Ext] getPair
- [Ext] allPairs
- [Ext] allPairsLength
- [Ext] createPair #
- [Ext] setFeeTo #
- [Ext] setFeeToSetter #
+ [Int] IERCBurn
- [Ext] burn #
- [Ext] approve #
- [Ext] allowance #
- [Ext] balanceOf
+ [Int] IMoonForceSwapPair
- [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 #
+ ReentrancyGuard
- [Int] #
+ Context
- [Int] _msgSender
- [Int] _msgData
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ [Lib] SafeMath
- [Int] add
- [Int] sub
- [Int] sub
- [Int] mul
- [Int] div
- [Int] div
- [Int] mod
- [Int] mod
+ [Lib] EnumerableSet
- [Prv] _add #
- [Prv] _remove #
- [Prv] _contains
- [Prv] _length
- [Prv] _at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
+ [Lib] TransferHelper
- [Int] safeApprove #
- [Int] safeTransfer #
- [Int] safeTransferFrom #
- [Int] safeTransferBaseToken #
+ MoonForceLocker (Ownable, ReentrancyGuard)
- [Pub] #
- [Pub] setDev #
- modifiers: onlyOwner
- [Pub] setMigrator #
- modifiers: onlyOwner
- [Pub] setSecondaryFeeToken #
- modifiers: onlyOwner
- [Pub] setReferralTokenAndHold #
- modifiers: onlyOwner
- [Pub] setFees #
- modifiers: onlyOwner
- [Pub] whitelistFeeAccount #
- modifiers: onlyOwner
- [Ext] lockLPToken ($)
- modifiers: nonReentrant
- [Ext] relock #
- modifiers: nonReentrant
- [Ext] withdraw #
- modifiers: nonReentrant
- [Ext] incrementLock #
- modifiers: nonReentrant
- [Ext] splitLock ($)
- modifiers: nonReentrant
- [Ext] transferLockOwnership #
- [Ext] migrate #
- modifiers: nonReentrant
- [Ext] getNumLocksForToken
- [Ext] getNumLockedTokens
- [Ext] getLockedTokenAtIndex
- [Ext] getUserNumLockedTokens
- [Ext] getUserLockedTokenAtIndex
- [Ext] getUserNumLocksForToken
- [Ext] getUserLockForTokenAtIndex
- [Ext] getWhitelistedUsersLength
- [Ext] getWhitelistedUserAtIndex
- [Ext] getUserWhitelistStatus
PresaleGenerator Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IPresaleFactory
- [Ext] registerPresale #
- [Ext] presaleIsRegistered
+ Context
- [Int] _msgSender
- [Int] _msgData
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ [Int] IPresaleSettings
- [Ext] getMaxPresaleLength
- [Ext] getRound1Length
- [Ext] userHoldsSufficientRound1Token
- [Ext] referrerIsValid
- [Ext] getBaseFee
- [Ext] getTokenFee
- [Ext] getEthAddress
- [Ext] getTokenAddress
- [Ext] getReferralFee
- [Ext] getEthCreationFee
+ [Int] IMoonForceSwapFactory
- [Ext] feeTo
- [Ext] feeToSetter
- [Ext] getPair
- [Ext] allPairs
- [Ext] allPairsLength
- [Ext] createPair #
- [Ext] setFeeTo #
- [Ext] setFeeToSetter #
+ [Int] IWETH
- [Ext] deposit ($)
- [Ext] transfer #
- [Ext] withdraw #
+ [Int] IERC20
- [Ext] decimals
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] allowance
- [Ext] approve #
- [Ext] transfer #
- [Ext] transferFrom #
+ [Int] IPresaleLockForwarder
- [Ext] lockLiquidity #
- [Ext] moonForcePairIsInitialised
+ ReentrancyGuard
- [Int] #
+ [Lib] EnumerableSet
- [Prv] _add #
- [Prv] _remove #
- [Prv] _contains
- [Prv] _length
- [Prv] _at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
+ [Lib] SafeMath
- [Int] add
- [Int] sub
- [Int] sub
- [Int] mul
- [Int] div
- [Int] div
- [Int] mod
- [Int] mod
+ [Lib] PresaleHelper
- [Pub] calculateAmountRequired
+ [Lib] TransferHelper
- [Int] safeApprove #
- [Int] safeTransfer #
- [Int] safeTransferFrom #
- [Int] safeTransferBaseToken #
+ Presale (ReentrancyGuard)
- [Pub] #
- [Ext] init1 #
- [Ext] init2 #
- [Pub] presaleStatus
- [Ext] userDeposit ($)
- modifiers: nonReentrant
- [Ext] userWithdrawTokens #
- modifiers: nonReentrant
- [Ext] userWithdrawBaseTokens #
- modifiers: nonReentrant
- [Ext] ownerWithdrawTokens #
- modifiers: onlyPresaleOwner
- [Ext] forceFailIfPairExists #
- [Ext] forceFailByMoonForce #
- [Ext] addLiquidity #
- modifiers: onlyPresaleOwner,nonReentrant
- [Ext] updateMaxSpendLimit #
- modifiers: onlyPresaleOwner
- [Ext] updateBlocks #
- modifiers: onlyPresaleOwner
- [Ext] setWhitelistFlag #
- modifiers: onlyPresaleOwner
- [Ext] editWhitelist #
- modifiers: onlyPresaleOwner
- [Ext] getWhitelistedUsersLength
- [Ext] getWhitelistedUserAtIndex
- [Ext] getUserWhitelistStatus
+ PresaleGenerator (Ownable)
- [Pub] #
- [Pub] createPresale ($)
PresaleSettings Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IERC20
- [Ext] decimals
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] allowance
- [Ext] approve #
- [Ext] transfer #
- [Ext] transferFrom #
+ [Lib] EnumerableSet
- [Prv] _add #
- [Prv] _remove #
- [Prv] _contains
- [Prv] _length
- [Prv] _at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
+ Context
- [Int] _msgSender
- [Int] _msgData
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ PresaleSettings (Ownable)
- [Pub] #
- [Ext] getRound1Length
- [Ext] getMaxPresaleLength
- [Ext] getBaseFee
- [Ext] getTokenFee
- [Ext] getReferralFee
- [Ext] getEthCreationFee
- [Ext] getEthAddress
- [Ext] getTokenAddress
- [Ext] setFeeAddresses #
- modifiers: onlyOwner
- [Ext] setFees #
- modifiers: onlyOwner
- [Ext] setRound1Length #
- modifiers: onlyOwner
- [Ext] setMaxPresaleLength #
- modifiers: onlyOwner
- [Ext] editAllowedReferrers #
- modifiers: onlyOwner
- [Ext] editEarlyAccessTokens #
- modifiers: onlyOwner
- [Ext] userHoldsSufficientRound1Token
- [Pub] getEarlyAccessTokenAtIndex
- [Pub] earlyAccessTokensLength
- [Ext] allowedReferrersLength
- [Ext] getReferrerAtIndex
- [Ext] referrerIsValid
PresaleFactory Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Lib] EnumerableSet
- [Prv] _add #
- [Prv] _remove #
- [Prv] _contains
- [Prv] _length
- [Prv] _at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
- [Int] add #
- [Int] remove #
- [Int] contains
- [Int] length
- [Int] at
+ Context
- [Int] _msgSender
- [Int] _msgData
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ PresaleFactory (Ownable)
- [Pub] adminAllowPresaleGenerator #
- modifiers: onlyOwner
- [Pub] registerPresale #
- [Ext] presaleGeneratorsLength
- [Ext] presaleGeneratorAtIndex
- [Ext] presaleIsRegistered
- [Ext] presalesLength
- [Ext] presaleAtIndex
PresaleLockForwarder Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IToken
- [Ext] getTotalFee #
+ [Int] IERC20
- [Ext] decimals
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] allowance
- [Ext] approve #
- [Ext] transfer #
- [Ext] transferFrom #
+ [Int] IPYESwapRouter01
- [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 ($)
+ [Int] IPYESwapRouter (IPYESwapRouter01)
- [Ext] removeLiquidityETHSupportingFeeOnTransferTokens #
- [Ext] removeLiquidityETHWithPermitSupportingFeeOnTransferTokens #
- [Ext] swapExactTokensForTokensSupportingFeeOnTransferTokens #
- [Ext] swapExactETHForTokensSupportingFeeOnTransferTokens ($)
- [Ext] swapExactTokensForETHSupportingFeeOnTransferTokens #
+ [Int] IMoonForceSwapPair
- [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] IMoonForceSwapFactory
- [Ext] feeTo
- [Ext] feeToSetter
- [Ext] getPair
- [Ext] allPairs
- [Ext] allPairsLength
- [Ext] createPair #
- [Ext] setFeeTo #
- [Ext] setFeeToSetter #
+ [Int] IMoonForceSwapLocker
- [Ext] lockLPToken ($)
+ [Int] IPresaleFactory
- [Ext] registerPresale #
- [Ext] presaleIsRegistered
+ [Lib] TransferHelper
- [Int] safeApprove #
- [Int] safeTransfer #
- [Int] safeTransferFrom #
- [Int] safeTransferBaseToken #
+ Context
- [Int] _msgSender
- [Int] _msgData
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
+ PresaleLockForwarder (Ownable)
- [Pub] #
- [Pub] moonForcePairIsInitialised
- [Ext] lockLiquidity #
About SourceHat
SourceHat (formerly Solidity Finance - founded in 2020) has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1700+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!
Contact us today to get a free quote for a smart contract audit of your project!
What is a SourceHat Audit?
Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.
How Do I Interpret the Findings?
Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:
- High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
- Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
- Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
- Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.