PolyStarter Platform - Smart Contract Audit Report
PolyStarter is a launchpad for projects to launch crowdsale campaigns in a safe, trusted, and decentralized manner.For this audit, we reviewed PolyStarter's Factory, Campaign, and Staker contracts using code provided to us by the project team.
Notes on the Contracts:
Only the owner can use the Factory contract to create and cancel Campaigns, but Campaign owners can control their Campaigns through a generated Campaign contract. Neither the project team nor the Campaign owner is able to modify any attibutes related to the Campaign after initialization. In order for investing to begin, the Campaign owner must fund the Campaign contract with the total amount of tokens for sale and for liquidity. Once a Campaign is funded, there is a Registration period in which anyone is able to register for a single tier in the IDO. In order to successfully register for the IDO, the user must have used the Staker contract to stake enough tokens to meet the minimum for the tier selected. The user's entire balance of staked tokens will be locked in the Staker contract for the lock duration decided on the Campaign's initialization. The owner of the platform can halt or unhalt staking at any time, as well as manually add any address as an IDO account in the Staking contract, giving permission to modify any user's lock duration on staked tokens. When the Campaign is in the Tier sale period, registered users can invest into the pool until they have purchased all the shares allocated to them according to the chosen tier, or until the pool reaches the hard capacity limit specified on initialization. After the Tier sale period is over, registered users can invest into the pool in the FCFS sale period until the pool reaches the hard capacity limit specified on initialization. When the sale period is over and the pool has met its capacity lower limit, or the pool has reached its hard capacity limit, Uniswap liquidity is funded by pairing MATIC from the pool with the Campaign token and adding it as liquidity to the MATIC pair; both MATIC and token amounts are fixed on initialization. The newly created LP tokens are stored in the contract and claimable by the Campaign owner once the lock duration has elapsed; the lock duration is set on initialization and cannot be changed. Any unspent MATIC or tokens meant for adding liquidity will be sent to the Campaign owner. A percentage of the MATIC collected is sent to the project team as platform fees; this percentage is fixed on initialization. After adding liquidity and taking fees, the remaining MATIC is sent to the Campaign owner. Any unsold tokens will be burned or sent to the owner, depending on the option set during initialization. Users can claim their tokens based on their investment amount one time only when the project team or the Campaign owner has set tokens as claimable. The Campaign can be cancelled only before the process to add liquidity and claim tokens has started. The Campaign will be considered failed in the event that the Campaign time has elapsed and the minimum capacity for the pool has not been met. Given the Campaign has been cancelled or has failed, the Campaign owner will be able to withdraw his pledged Campaign tokens and participants will be able to withdraw their invested MATIC. The team has worked with us to improve these contracts for gas optimization. Proper structuring of logic around base layer token transfers to prevent reentrancy issues. The contracts utilize SafeMath to prevent overflow issues.
Audit Findings Summary:
- No security issues from outside attackers were identified.
- The platform should not be used with ERC-777 tokens to prevent re-entrancy issues. This is uncommon.
- Ensure trust in the project team as they have some control in the ecosystem.
- Date: July 12th, 2021.
|Arbitrary Storage Write||N/A||PASS|
|Delegate Call to Untrusted Contract||N/A||PASS|
|Dependence on Predictable Variables||N/A||PASS|
|State Change External Calls||N/A||Pass|
|User Supplied Assertion||N/A||PASS|
|Critical Solidity Compiler||N/A||PASS|
|Overall Contract Safety||PASS|
($) = payable function # = non-constant function Int = Internal Ext = External Pub = Public + Ownable - [Pub]
# - [Pub] transferOwnership # - modifiers: onlyOwner - [Pub] renounceOwnership # - modifiers: onlyOwner + [Lib] Address - [Int] isContract - [Int] sendValue # - [Int] functionCall # - [Int] functionCall # - [Int] functionCallWithValue # - [Int] functionCallWithValue # - [Int] functionStaticCall - [Int] functionStaticCall - [Int] functionDelegateCall # - [Int] functionDelegateCall # - [Prv] _verifyCallResult + [Int] IERC20 - [Ext] totalSupply - [Ext] balanceOf - [Ext] transfer # - [Ext] allowance - [Ext] approve # - [Ext] transferFrom # + Context - [Int] _msgSender - [Int] _msgData + [Lib] SafeMath - [Int] mul - [Int] div - [Int] sub - [Int] add + ERC20 (Context, IERC20) - [Pub] # - [Pub] name - [Pub] symbol - [Pub] decimals - [Pub] totalSupply - [Pub] balanceOf - [Pub] transfer # - [Pub] allowance - [Pub] approve # - [Pub] transferFrom # - [Pub] increaseAllowance # - [Pub] decreaseAllowance # - [Int] _transfer # - [Int] _mint # - [Int] _burn # - [Int] _approve # - [Int] _setupDecimals # - [Int] _beforeTokenTransfer # + Staker (Context, Ownable) - [Pub] # - [Ext] stakedBalance - [Ext] unlockTime - [Ext] isIDO - [Ext] stake # - modifiers: notHalted - [Ext] unstake # - modifiers: lockable - [Ext] lock # - modifiers: onlyIDO - [Ext] halt # - modifiers: onlyOwner - [Ext] addIDO # - modifiers: onlyOwner + [Lib] SafeERC20 - [Int] safeTransfer # - [Int] safeTransferFrom # - [Int] safeApprove # - [Int] safeIncreaseAllowance # - [Int] safeDecreaseAllowance # - [Prv] _callOptionalReturn # + [Int] IFactoryGetters - [Ext] getLpRouter - [Ext] getFeeAddress - [Ext] getLauncherToken - [Ext] getStakerAddress + ReEntrancyGuard + [Int] IUniswapV2Router02 - [Ext] addLiquidityETH ($) + Campaign (ReEntrancyGuard) - [Pub] # - [Ext] initialize # - [Pub] isInRegistration - [Pub] isInTierSale - [Pub] isInFCFS - [Pub] isInEnd - [Pub] currentPeriod - [Pub] userRegistered - [Pub] userPurchased - [Pub] userTier - [Pub] userAllocation - [Pub] userMaxInvest - [Pub] userMaxTokens - [Ext] fundIn # - modifiers: onlyCampaignOwner - [Ext] fundOut # - modifiers: onlyCampaignOwner - [Pub] registerForIDO # - modifiers: noReentrant - [Pub] buyTierTokens ($) - modifiers: noReentrant - [Pub] buyFCFSTokens ($) - modifiers: noReentrant - [Int] addAndLockLP # - [Pub] getPoolLP - [Ext] recoverUnspentLp # - modifiers: onlyFactory - [Pub] finishUp # - [Ext] setTokenClaimable # - modifiers: onlyFactoryOrCampaignOwner - [Ext] claimTokens # - modifiers: noReentrant - [Pub] withdrawLP # - modifiers: onlyCampaignOwner - [Pub] refund # - [Pub] getClaimableTokenAmt - [Int] sendTokensTo # - [Int] getFeeAmt - [Int] getFeeAddress - [Pub] failedOrCancelled - [Pub] isLive - [Pub] calculateTokenAmount - [Pub] getRemaining - [Pub] setCancelled # - modifiers: onlyFactory - [Pub] getCampaignFundInTokensRequired - [Int] lockTokens # + Factory (IFactoryGetters, Ownable) - [Pub] # - modifiers: Ownable - [Pub] createCampaign # - modifiers: onlyOwner - [Ext] cancelCampaign # - modifiers: onlyOwner - [Ext] recoverUnspentLp # - modifiers: onlyOwner - [Ext] getLpRouter - [Ext] getFeeAddress - [Ext] getLauncherToken - [Ext] getStakerAddress