Toro Coin Offering & ICO Platform - Smart Contract Audit Report
Toro is Gain Protocol's new Coin Offering platform to be used with the project team's $GAIN token.
We reviewed Gain Protocol's Toro, CoinOffering, and InitialCoinOffering contracts at commit cdef5973ffcffe0c15acc8f8f1c1fc2b837de44a on GitHub.
We previously reviewed the team's GainProtocol contracts here.
Notes on the Contracts:
- The Toro contract is used to manage rounds of CoinOfferings and streamline the CoinOffering functionality for the project team's $GAIN token.
- The Initial Coin Offering contract is used to initialize a new CoinOffering.
- The owner is able to add new rounds to the offering at will, as long as the minimum USD amount of $2000 worth of $GAIN tokens is met and the duration of the round meets the 2 day minimum.
- The tokens offered in a round are held in the contract address.
- When a new round is scheduled, the rounds are re-sorted in descending order by total token offering value in USD, so the most valuable rounds will happen first.
- Due to this reordering, the project team must schedule all Toro rounds before starting any rounds to prevent the next round pointer from becoming misaligned; otherwise rounds may be lost during scheduling.
- Once rounds are scheduled, anyone will be able to kick off a new round given there is not a round currently running, there is another round to kick off, and the previous round (if any) has been finalized.
- Rounds will be skipped if there are not enough $GAIN tokens in the contract balance to cover the round.
- The offering hard capacity for the round is determined by taking either the BNB value of the tokens allocated for the round, or 41.421% of the BNB stored in liquidity.
- Users can contribute to a round that is still accepting contributions as long as the hard capacity for the round has not been met, and the contribution amount is between the pre-defined minimum and maximum, inclusive of the bounds.
- Contributing via the Toro contract will enhance the user's experience by first checking if the current round has ended, completing the finalizing process, starting the next round, and depositing the user's contribution to the next round.
- On finalization, the BNB collected is swapped for $GAIN tokens and distributed to $GAIN holders as a static reward.
- An offering that has ended and is finalized before the soft capacity is met will undergo cancellation.
- If cancellation occurs on the last round, the leftover $GAIN will be sent to all $GAIN holders as a static reward; this is also the case for the ICO.
- A set amount of tokens are paired with BNB and added as liquidity to the BNB pair.
- The LP Tokens are stored in the $GAIN token contract address and cannot be withdrawn at any time.
- Users who have contributed can use the CoinOffering contract to claim given the offering period has ended and has not failed.
- A token amount equivalent to the user's BNB contribution amount will be transferred to the user's wallet address and accessible once the lock period has expired; the lock duration is set on initialization of the Coin Offering contract and can only change while restarting an offering.
- Users are able to withdraw their contributed BNB in the event that the offering has failed.
- The owner is able to set the price feed contract address at any time.
- The owner is able to take any amount of $GAIN tokens in the Toro contract and use them to deliver static rewards to $GAIN token holders.
- The team has implemented recommendations to improve the gas effeciency of the contracts.
- The code is well-commented to explain logic.
- Utilization of SafeMath to prevent overflows.
Audit Findings Summary:
- No security issues from outside attackers were identified.
- As with any coin offering, ensure trust in the team prior to investing.
- Ensure trust in the team as they have some control in the ecosystem.
- Date: July 12th, 2021.
External Threat Results
|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 + [Int] IERC20 - [Ext] totalSupply - [Ext] balanceOf - [Ext] transfer # - [Ext] allowance - [Ext] approve # - [Ext] transferFrom # + [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] 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] IGainProtocol (IERC20) - [Ext] startDraw # - [Ext] setAssociate # - [Ext] dailyTransfersOf - [Ext] tokenFromReflection - [Ext] reflectionFromToken - [Ext] collectedSweepstake - [Ext] collectedLiquidity - [Ext] collectedCharity - [Ext] collectedWhaleFee - [Ext] collectedReward - [Ext] collectedTeamFee - [Ext] collectedHodlReward - [Ext] soldLiquidity - [Ext] availableSweepstake - [Ext] availableCharity - [Ext] availableLiquidity - [Ext] transferSweepstake # - [Ext] calculateFees - [Ext] lockedBalanceReleaseDate - [Ext] lockedBalanceOf - [Ext] hodlTokensOf - [Ext] giveBack # - [Ext] giveBackHodl # - [Ext] lockedTransfer # - [Ext] lockTokens # - [Ext] uniswapV2Pair - [Ext] uniswapV2Router + [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 + Context - [Int] _msgSender - [Int] _msgData + Ownable (Context) - [Int]
# - [Pub] owner - [Pub] renounceOwnership # - modifiers: onlyOwner - [Pub] transferOwnership # - modifiers: onlyOwner + CoinOffering (Ownable) - [Pub] # - [Pub] contribute ($) - [Ext] claim # - modifiers: offeredSuccessfully - [Ext] returnContribution # - [Pub] wasStarted - [Pub] hasFailed - [Pub] wasFinalized - [Pub] hasEnded - [Pub] isAcceptingContribution - [Ext] finalize # - modifiers: onlyOwner - [Int] _finalize # - [Int] _restartOffering # - [Prv] _cancelOffering # - [Prv] _tokenBalance - [Prv] _rewardLeftover # - [Prv] _addInitialLiquidity # - [Prv] _buyTokensWithBNB # - [Prv] _isAcceptingContribution - [Int] _min + [Int] AggregatorV3Interface - [Ext] decimals - [Ext] description - [Ext] version - [Ext] getRoundData - [Ext] latestRoundData + GainPriceFeed - [Pub] # - [Ext] gainsForUSD - [Ext] getReserves - [Pub] usdBNBPrice - [Ext] usdBNBDecimals - [Ext] usdToBNB + Toro (Context, Ownable, CoinOffering) - [Pub] # - modifiers: CoinOffering - [Ext] getRoundCount - [Ext] getRound - [Ext] startNewRound # - [Pub] contribute ($) - [Ext] manualGiveback # - modifiers: onlyOwner - [Ext] setPriceFeed # - modifiers: onlyOwner - [Ext] setLockTime # - modifiers: onlyOwner - [Ext] changeDuration # - modifiers: onlyOwner - [Ext] scheduleRound # - modifiers: onlyOwner - [Prv] _startNewRound #