Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Storage Write | N/A | PASS |
Arbitrary Jump | N/A | PASS |
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 | N/A | PASS |
Integer Over/Underflow | 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 |
BotGains - Smart Contract Audit Report
Summary
BotGains is a crypto trading pool that intends to allow users to deposit ETH and have the team trade with the funds with the goal of making profits and returning those profits to investors. Botgains intends to do this by utilizing varius types of trading bots to execute profitable trades.
We audited contracts that the BotGains team provided to us. Please note we have not reviewed Botgains trading strategies.Findings on the Contracts:
- BotGains allows users to deposit ETH that the Botgains will use to trade with and provide back a portion of the profits to depositers.
- Depositing with BotGains requires committing to a lockup period, currently set at 30 days. Each month rewards will be added to the contract and available for distribution.
- After each period, users will have the option of withdrawing funds or reinvesting in the platform.
- The fees associated with investing are a 2% fee on deposits, reinvests, and dividends paid (collected by the admin address); a 20% management fee on profits earned; and an additional .25% fee on profits earned for the funding of a future “Loyalty Bonus”.
- The owner can also change a number of variables in the smart contract. These include: The minimum/maximum amount ETH to invest, the maximum pool size, lock/unlock times for deposits, and wallet addresses for the following uses: Bot, Owner, Admin, Management, Divs, Bonus, & trading.
- Each of these addresses have different uses. Only the address set to be the Bot is meant to withdraw ETH under normal circumstances; and only the Bonus address can send bonuses to the contract properly.
- Usage of ReentrancyGuard in applicable functions to prevent re-entrancy attacks.
- Utilization of SafeMath and SafeERC20 to prevent overflows and ensure safe transfers.
- No security issues from outside attackers were identified.
- Investing requires placing substantial trust in the team as they must send the ETH back to the investment contract manually.
- Date: December 29th, 2020.
- Update Date: February 21st, 2021 - Review V1.5 code.
BotGains includes the following disclaimer on their site: No Guarantee of Profits. Funds given as a donation to BotGains admins to trade with. No losses to be represented nor paid back. The Pool Trader is in no respects making any guarantee of profits or of protections against loss, but is undertaking to use reasonable best efforts to trade profitably on behalf of the user pool.
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ Context
- [Int] #
- [Int] _msgSender
- [Int] _msgData
+ [Lib] SafeMath
- [Int] add
- [Int] sub
- [Int] sub
- [Int] mul
- [Int] div
- [Int] div
- [Int] mod
- [Int] mod
+ Ownable (Context)
- [Int] #
- [Pub] owner
- [Pub] isOwner
- [Pub] renounceOwnership #
- modifiers: onlyOwner
- [Pub] transferOwnership #
- modifiers: onlyOwner
- [Int] _transferOwnership #
+ BotGainsProtocolStorage (Ownable)
- [Pub] #
- [Ext] changeCycleLength #
- modifiers: onlyOwner
- [Pub] _cycleLength
- [Ext] changeLockTimer #
- modifiers: onlyOwner
- [Ext] changeUnlockTimer #
- modifiers: onlyOwner
- [Pub] _lockTimer
- [Pub] _unlockTimer
- [Ext] changeMinETH #
- modifiers: onlyOwner
- [Ext] changeMaxETH #
- modifiers: onlyOwner
- [Ext] changeMaxPoolSize #
- modifiers: onlyOwner
- [Pub] _minETH
- [Pub] _maxETH
- [Pub] _maxPoolSize
- [Ext] changeAdminFeeWallet #
- modifiers: onlyOwner
- [Ext] changeManagementFeeWallet #
- modifiers: onlyOwner
- [Ext] changeDivsFeeWallet #
- modifiers: onlyOwner
- [Ext] changeBonusWallet #
- modifiers: onlyOwner
- [Ext] changeTradingWallet #
- modifiers: onlyOwner
- [Pub] _tradingWallet
- [Pub] _adminFeeWallet
- [Pub] _managementFeeWallet
- [Pub] _divsFeeWallet
- [Pub] _bonusWallet
+ ReentrancyGuard
- [Int] #
+ BotGainsProtocol (Ownable, ReentrancyGuard)
- [Pub] #
- [Ext] ($)
- [Pub] userDeposit ($)
- modifiers: isUnlocked,nonReentrant
- [Pub] userWithdrawFundsOnCycle #
- modifiers: nonReentrant,isUnlocked
- [Ext] BOTwithdraw #
- modifiers: onlyBot,isUnlocked
- [Ext] BOTdeposit ($)
- modifiers: onlyBot,isNotUnlocked
- [Int] incrementUser #
- [Int] transferToDivs #
- [Int] transferToAdmin #
- [Int] transferToManagement #
- [Int] checkUserLimit
- [Int] calculateEth
- [Ext] viewFundsAvailable
- [Ext] viewCurrentlyInvested
- [Ext] viewPool
Click here to download the source code as a .sol file.
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;
import "./BotGainsProtocolStorage.sol";
import "@openzeppelin/audits/math/SafeMath.sol";
import "@openzeppelin/audits/ownership/Ownable.sol";
import "@openzeppelin/audits/utils/ReentrancyGuard.sol";
contract BotGainsProtocol is Ownable,ReentrancyGuard {
using SafeMath for uint256;
BotGainsProtocolStorage private _protocol_storage;
//state variables
bool public locked = false;
uint256 public currentCycle;
bool public loss;
uint256 private fragsPerETH;
uint256 public timeLocked;
uint256 public timeUnlocked;
//cycle mappings
mapping(uint256 => mapping(address => bool)) public userExistOnCycle;
mapping(uint256 => uint256) private TOTAL_FRAGS_ON_CYCLE; //tracks total frags on a given cycle
mapping(uint256 => uint256) private FRAGS_PER_ETH_ON_CYCLE;
mapping(uint256 => mapping(address => uint256)) private USER_FRAGS_ON_CYCLE; //tracks capital on a given cycle\
mapping(uint256 => uint256) public USERS_ON_CYCLE;
mapping(uint256 => uint256) public POOL_ON_CYCLE;
mapping(uint256 => bool) public PROFIT_ON_CYCLE;
modifier cycleHappend(){
require(currentCycle > 0, "no cycles have occured yet");
_;
}
modifier isUnlocked() {
require(!locked, "The bot is current trading!");
_;
}
modifier isNotUnlocked() {
require(locked, "The bot is currently trading!");
_;
}
modifier onlyBot() {
require(msg.sender == _protocol_storage._tradingWallet(), "Not the trader!");
_;
}
modifier onlyBonus() {
require(msg.sender == _protocol_storage._bonusWallet(), "Not the Bonus Wallet");
_;
}
event BotWithdraw(uint256 poolAmount, uint256 users, uint256 atTime, uint256 onCycle);
event BotDeposit(uint256 depositAmount, uint256 forCycle, bool loss, uint256 atTime);
event UserDeposit(uint256 depositAmount, uint256 onCycle, uint256 atTime);
event UserWithdraw(uint256 poolAmount, uint256 onCycle, uint256 atTime);
constructor (address _storage) public {
_protocol_storage = BotGainsProtocolStorage(_storage);
//assign state variables
currentCycle = 0;
fragsPerETH = 1e30;
FRAGS_PER_ETH_ON_CYCLE[0] = fragsPerETH;
timeUnlocked = now;
timeLocked=0;
USERS_ON_CYCLE[currentCycle] = 0;
}
//revert payable fallback
function() payable external {
revert("Use userDeposit");
}
/******************************************
************* USER FUNCTIONS **************
*******************************************/
//userDeposit:
function userDeposit() public payable isUnlocked nonReentrant {
require(_protocol_storage._minETH() <= msg.value, "Minimum not met"); //good
incrementUser(_msgSender()); //good
uint256 userETHamount = msg.value.mul(97750).div(1e5); //97.75%
uint256 userFeeAmount = msg.value.mul(2000).div(1e5); //2%
uint256 userDivFeeAmount = msg.value.mul(250).div(1e5); //.25%
//check user deposit into current pool & check that this deposit does not exceed pool limit
checkUserLimit(_msgSender(), userETHamount);
//update this user's balance for this investment cycle
uint256 fragAmount = (userETHamount).mul(FRAGS_PER_ETH_ON_CYCLE[currentCycle]); //user frag balance -- good
//if multiple deposits have been made this step makes sure frags have been updates correctly with additional deposit
USER_FRAGS_ON_CYCLE[currentCycle][_msgSender()] = USER_FRAGS_ON_CYCLE[currentCycle][_msgSender()].add(fragAmount);
//keep track of total frags on this cycle
TOTAL_FRAGS_ON_CYCLE[currentCycle] = TOTAL_FRAGS_ON_CYCLE[currentCycle].add(fragAmount); // good -- track totals
//add liquidity to pool for this round of investment
POOL_ON_CYCLE[currentCycle] = POOL_ON_CYCLE[currentCycle].add(userETHamount); //good -- updates total ETH alongside FRAGS
userExistOnCycle[currentCycle][_msgSender()] = true;
//transfer fees
transferToAdmin(userFeeAmount); // good
transferToDivs(userDivFeeAmount); // good
emit UserDeposit(userETHamount, currentCycle, block.timestamp);
}
//userWithdraw:
function userWithdrawFundsOnCycle(uint256 _cycle) public nonReentrant isUnlocked{
//get user frags on this cycle
uint256 fragAmount = USER_FRAGS_ON_CYCLE[_cycle][_msgSender()]; // good
if(USER_FRAGS_ON_CYCLE[_cycle][_msgSender()] == 0) {
revert("BotGains: Nothing to Withdraw");
}
//convert these frags to eth amount
uint256 ETHamount = calculateEth(fragAmount, _cycle); // good
//send to user
_msgSender().transfer(ETHamount); // good
//zero them out
USER_FRAGS_ON_CYCLE[_cycle][_msgSender()] = 0; // good
//subtract FRAGS from TOTAL on this cycle
TOTAL_FRAGS_ON_CYCLE[_cycle] = TOTAL_FRAGS_ON_CYCLE[_cycle].sub(fragAmount); // good
//update the current cycle pool stats
if(_cycle == currentCycle){
POOL_ON_CYCLE[_cycle] = POOL_ON_CYCLE[_cycle].sub(ETHamount); // good
//update user count
USERS_ON_CYCLE[_cycle] = USERS_ON_CYCLE[_cycle].sub(1); // good
userExistOnCycle[_cycle][_msgSender()] = false;
}
emit UserWithdraw(ETHamount, _cycle, block.timestamp);
}
/****************
* BOT FUNCTIONS *
*****************/
function BOTwithdraw() external onlyBot isUnlocked {
//withdraw the pool amount for this investment cycle
uint256 poolAmount = POOL_ON_CYCLE[currentCycle];
_protocol_storage._tradingWallet().transfer(poolAmount);
//lock the user FUNCTIONS
locked = true;
timeLocked = now;
//emit event to show the amount withdrawn, users, and time
emit BotWithdraw(poolAmount, USERS_ON_CYCLE[currentCycle], block.timestamp, currentCycle);
}
function BOTdeposit() payable onlyBot isNotUnlocked external {
uint256 ETHamount = msg.value;
if(ETHamount == 0){
//if the eth amount here is 0 the previous cycle pool amount can NOT be 0 as well
if(POOL_ON_CYCLE[currentCycle] != 0) {
revert("deposit and pool funds must both be zero or both be non zero");
}
} else{
//previous pool cycle CANNOT BE ZERO if deposit is not zero!
if(POOL_ON_CYCLE[currentCycle] == 0){
revert("deposit and pool funds must both be zero or both be non zero");
}
if (POOL_ON_CYCLE[currentCycle] <= ETHamount){ // WIN!
//subtract eth amount from pool amount to get profit amount and take 20%
uint256 profit = ETHamount.sub(POOL_ON_CYCLE[currentCycle]);
uint256 forManagement = profit.mul(20).div(100);
uint256 ethForPool = ETHamount.sub(forManagement);
//transfer 20% of profit to management
transferToManagement(forManagement);
//caluclate the new fragsPerEth on this cycle
FRAGS_PER_ETH_ON_CYCLE[currentCycle] = TOTAL_FRAGS_ON_CYCLE[currentCycle].div(ethForPool); // good
//+1 TO ACCOUNT FOR THE NEXT CYCLE's FRAG CONVERSION
FRAGS_PER_ETH_ON_CYCLE[currentCycle.add(1)] = fragsPerETH;
loss = false;
} else{ // LOSS!
//caluclate the new fragsPerEth on this cycle
FRAGS_PER_ETH_ON_CYCLE[currentCycle] = TOTAL_FRAGS_ON_CYCLE[currentCycle].div(ETHamount); // good
//+1 TO ACCOUNT FOR THE NEXT CYCLE's FRAG CONVERSION
FRAGS_PER_ETH_ON_CYCLE[currentCycle.add(1)] = fragsPerETH;
loss = true;
}
}
//increment cycle
currentCycle = currentCycle.add(1);
//init users
USERS_ON_CYCLE[currentCycle] = 0;
//unlock
locked = false;
timeUnlocked = now;
emit BotDeposit(ETHamount, currentCycle.sub(1), loss, block.timestamp);
}
/*internal utils*/
function incrementUser(address _user) internal {
if(!userExistOnCycle[currentCycle][_user]){
userExistOnCycle[currentCycle][_user] = true;
USERS_ON_CYCLE[currentCycle] = USERS_ON_CYCLE[currentCycle].add(1);
}
}
function transferToDivs(uint256 amount) internal{
_protocol_storage._divsFeeWallet().transfer(amount);
}
function transferToAdmin(uint256 amount) internal{
_protocol_storage._adminFeeWallet().transfer(amount);
}
function transferToManagement(uint256 amount) internal{
_protocol_storage._managementFeeWallet().transfer(amount);
}
function checkUserLimit(address _user, uint256 amount) view internal{
if((USER_FRAGS_ON_CYCLE[currentCycle][_user].div(FRAGS_PER_ETH_ON_CYCLE[currentCycle])).add(amount) > _protocol_storage._maxETH()){
revert("This user has reached the maximum limit for deposits!");
}
if(POOL_ON_CYCLE[currentCycle].add(amount) > _protocol_storage._maxPoolSize()){
revert("Maximum amount of ETH reached");
}
}
function calculateEth(uint256 fragAmount, uint256 _cycle) internal view returns(uint256){
return fragAmount.div(FRAGS_PER_ETH_ON_CYCLE[_cycle]);
}
/***********************
* PUBLIC UTIL FUNCTIONS *
************************/
function viewFundsAvailable(uint256 _cycle, address _user) external view returns(uint256) {
uint256 _userFrags = USER_FRAGS_ON_CYCLE[_cycle][_user];
if(_userFrags == 0){
return 0;
}
uint256 _fragsPerETH = FRAGS_PER_ETH_ON_CYCLE[_cycle];
return _userFrags.div(_fragsPerETH);
}
function viewCurrentlyInvested(address _user) external view returns(uint256) {
if(USER_FRAGS_ON_CYCLE[currentCycle][_user] == 0){
return 0;
}
return USER_FRAGS_ON_CYCLE[currentCycle][_user].div(FRAGS_PER_ETH_ON_CYCLE[currentCycle]);
}
function viewPool(uint256 _cycle) external view returns(uint256){
return POOL_ON_CYCLE[_cycle];
}
}
Click here to download the source code as a .sol file.
// SPDX-License-Identifier: ISC
pragma solidity ^0.5.0;
import "hardhat/console.sol";
import "@openzeppelin/audits/math/SafeMath.sol";
import "@openzeppelin/audits/ownership/Ownable.sol";
contract BotGainsProtocolStorage is Ownable{
using SafeMath for uint256;
//addressstate variable
address payable botWallet;
address payable adminFeeWallet;
address payable managementFeeWallet;
address payable divsFeeWallet;
address payable bonusWallet;
uint256 public minETH = 1e17;
uint256 public maxETH = 1e21; //1000 eth
uint256 public maxPoolSize = 500e18;
uint256 public cycleLength;
uint256 private lockTimer;
uint256 private unlockTimer;
constructor (address payable _tradingWallet,address payable _adminFeeWallet,address payable _managementFeeWallet,address payable _divsFeeWallet, address payable _bonusWallet) public {
//assign wallet addresses
botWallet = _tradingWallet;
adminFeeWallet = _adminFeeWallet;
managementFeeWallet = _managementFeeWallet;
divsFeeWallet = _divsFeeWallet;
bonusWallet = _bonusWallet;
cycleLength = 30;
unlockTimer= 30 minutes;
lockTimer= 30 minutes;
}
function changeCycleLength(uint256 _days) onlyOwner external {
require(_days > 1, "must be greater than a single day");
cycleLength = _days;
}
function _cycleLength() public view returns(uint256) {
return cycleLength;
}
function changeLockTimer(uint256 _minutes) onlyOwner external {
lockTimer = _minutes.mul(1 minutes);
}
function changeUnlockTimer(uint256 _minutes) onlyOwner external {
unlockTimer = _minutes.mul(1 minutes);
}
function _lockTimer() public view returns(uint256){
return lockTimer;
}
function _unlockTimer() public view returns(uint256){
return unlockTimer;
}
function changeMinETH(uint256 amount) onlyOwner external {
require(amount > 1e14, "Min ETH can be no lower than 0.001 ETH, Make sure to update the minETH in wei");
minETH = amount;
}
function changeMaxETH(uint256 amount) onlyOwner external {
require(amount > 1e18, "Max ETH can be no lower than 1 ETH, Make sure to update the maxETH in wei");
maxETH = amount;
}
function changeMaxPoolSize(uint256 amount) onlyOwner external {
require(amount > 1e18, "Max ETH can be no lower than 1 ETH, Make sure to update the maxPoolSize in wei");
maxPoolSize = amount;
}
function _minETH() public view returns(uint256){
return minETH;
}
function _maxETH() public view returns(uint256){
return maxETH;
}
function _maxPoolSize() public view returns(uint256){
return maxPoolSize;
}
function changeAdminFeeWallet(address payable _admin) onlyOwner external{
adminFeeWallet = _admin;
}
function changeManagementFeeWallet(address payable _manage) onlyOwner external{
managementFeeWallet = _manage;
}
function changeDivsFeeWallet(address payable _divs) onlyOwner external{
divsFeeWallet = _divs;
}
function changeBonusWallet(address payable _bonus) onlyOwner external{
bonusWallet = _bonus;
}
function changeTradingWallet(address payable _tradingWallet) onlyOwner external{
botWallet = _tradingWallet;
}
function _tradingWallet() public view returns(address payable){
return botWallet;
}
function _adminFeeWallet() public view returns(address payable){
return adminFeeWallet;
}
function _managementFeeWallet() public view returns(address payable){
return managementFeeWallet;
}
function _divsFeeWallet() public view returns(address payable){
return divsFeeWallet;
}
function _bonusWallet() public view returns(address payable){
return bonusWallet;
}
}