// File: AggregatorV3Interface.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  function getRoundData(uint80 _roundId)
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}
// File: bbcstakingcontract.sol



 pragma solidity ^0.8.0;

//SPDX-License-Identifier: Unlicensed

import "./ITeam.sol";
import "./IERC20.sol";
import "./Ownable.sol";
import "./AggregatorV3Interface.sol";
import "./SafeMath.sol";
import "./SignedSafeMath.sol";


interface IUniswapV2Pair {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);    
}

interface IUniswapV2Router {
    function getAmountsIn(uint amountIn, address[] memory path) external view returns (uint[] memory amounts);
}


contract BBCStakingContract is Ownable {
    
    using SignedSafeMath for int256;    
    using SafeMath for uint256;

    address immutable WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address immutable DAI_ADDRESS = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    IUniswapV2Pair immutable DAI_PAIR = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11);
    IUniswapV2Router immutable UNISWAP_ROUTER_V2 = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);   

    address public _bearAddress;
    address public _bullAddress;
    address public _crabAddress;

    address public _mainAddress;

    ITeam private _bearToken;
    ITeam private _bullToken;
    ITeam private _crabToken;

    IERC20 private _mainToken;

    uint256 public _startTime;
    uint256 public _endTime;

    int256 public _openPrice;
    int256 public _closePrice;
    int256 public _currentPrice;
    uint256 public lastTimeChecked;
    uint256 public _prize;

    string public _name;
    string public _symbol;

    //percentage change below which we can confidently say: we crabbin'
    int256 public _crabPercentage;

    //0 for Chainlink, 1 for UniswapV2
    bool public _betType;

    //Chainlink Stuff
    address public _linkAggregatorAddress;
    AggregatorV3Interface private priceFeed;
    uint8 public oracleDecimals;

    //Univ2stuff
    IERC20 public _token;
    IERC20 public _otherToken;
    IUniswapV2Pair public _pair;
    uint8 _tokenDecimals;
    uint8 _otherTokenDecimals;
    bool _tokenPosition;

    game[] public games;
    uint public counter = 0;

  

    struct game {
        string name;
        string symbol;
        uint256 startTime;
        uint256 endTime;
        uint256 prize;
        string winner;
    }

    constructor (address bullAddress, address bearAddress, address crabAddress, address mainAddress) {
        _bullAddress= bullAddress;
        _bearAddress = bearAddress;
        _crabAddress = crabAddress;
        _mainAddress = mainAddress;

        _bullToken = ITeam(_bullAddress);
        _bearToken = ITeam(_bearAddress);
        _crabToken = ITeam(_crabAddress);

        _mainToken = IERC20(_mainAddress); 
    
    }

    receive() external payable {}


    //for data available through ChainLink
    function setOracleBet(
        string calldata name,
        string calldata symbol,
        address linkAggregatorAddress,
        int256 crabPercentage, 
        uint256 startTime, 
        uint256 endTime
    ) external payable onlyOwner {

        _name = name;
        _symbol = symbol;

        _betType = false;
        _crabPercentage = crabPercentage;
        _startTime = startTime;
        _endTime = endTime;

        _linkAggregatorAddress = linkAggregatorAddress;
        priceFeed = AggregatorV3Interface(_linkAggregatorAddress);  
        oracleDecimals = priceFeed.decimals();

        _prize = msg.value;      

        getPrice();
    }
    
    //for Uniswap V2 pools
    function setUniV2Bet(
        string calldata name,
        string calldata symbol,
        address token,
        address pair, 
        int256 crabPercentage, 
        uint256 startTime, 
        uint256 endTime
    ) external payable onlyOwner {

        _name = name;
        _symbol = symbol;

        _token = IERC20(token);
        _pair = IUniswapV2Pair(pair);   

        if (_pair.token0() == token) {
            _tokenPosition = false;
            _otherToken = IERC20(_pair.token1());
        }
        else if (_pair.token1() == address(token)) {
            _tokenPosition = true;
            _otherToken = IERC20(_pair.token0());
        }
        else require(false, "invalid pair");

        _tokenDecimals = _token.decimals();
        _otherTokenDecimals = _otherToken.decimals();

        _betType = true;
       
        _crabPercentage = crabPercentage;
        _startTime = startTime;
        _endTime = endTime;

        _prize = msg.value;

        getPrice();
    } 
       
    

    function getPrice() public {

        int256 _gudPrice;

        if (!_betType) {
            //get price with ChainLink
            (
            /*int2560 roundID*/,
            _gudPrice,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*int2560 answeredInRound*/
            ) = (priceFeed.latestRoundData());
            _currentPrice=_gudPrice;
                        
        }
        else {
            address[] memory path1 = new address[](2);
            path1[0] = address(_otherToken);
            path1[1] = address(_token);

            uint256 price = UNISWAP_ROUTER_V2.getAmountsIn(1*10**_tokenDecimals, path1)[0];                

            //if it's not a stablecoin pool, convert ETH value to USD
            if (address(_otherToken) == WETH) {
                address[] memory path2 = new address[](2);
                
                path2[1] = address(_otherToken);
                path2[0] = address(DAI_ADDRESS);               
                
                _gudPrice = int256(UNISWAP_ROUTER_V2.getAmountsIn(price, path2)[0]);                
            }
            else {
                _gudPrice = int256(price);
            }

            _currentPrice = _gudPrice;
        }

    }        

    function sendPrize() internal {
        game memory lastGame;
        lastGame.name = _name;
        lastGame.symbol = _symbol;
        lastGame.startTime = _startTime;
        lastGame.endTime = _endTime;
        lastGame.prize = _prize;
            
        address winner;
        int256 percentageChange = ((_closePrice.sub(_openPrice)).mul(100)).div(_openPrice);
        if (abs(percentageChange) < _crabPercentage) {
            winner = _crabAddress;
            lastGame.winner ="Crab";
        }
        else if (percentageChange > 0) {
            winner = _bullAddress;
            lastGame.winner = "Bull";
        }
        else {
            winner = _bearAddress;
            lastGame.winner =  "Bear";
        }
        games.push(lastGame);
        counter++;
        payable(winner).call{value: _prize}("");

        _openPrice = 0;
        _closePrice = 0;       
        _prize = 0;
    }

 
    
    function stake(uint256 amount, address team) external {
        checkTime();
        require (!isStakingClosed(), "Bets are closed: you cannot Stake at this time!");     
        require(_mainToken.balanceOf(msg.sender) >= amount, "Not enough tokens");

        _mainToken.transferFrom(msg.sender, address(this), amount);        
    
        if (team == _bearAddress) {
            _bearToken.stake(msg.sender, amount);
        }

        else if (team == _bullAddress) {
            _bullToken.stake(msg.sender, amount);
        }

        else if (team == _crabAddress) {
            _crabToken.stake(msg.sender, amount);
        }

        else {
            require(false, "Provide a valid team address");
        }

    }

    function unstake(uint256 amount, address team) external {
        checkTime();
        require (!isStakingClosed(), "Bets are closed: you cannot Unstake at this time!"); 

        if (team == _bearAddress) {
            require (amount <= _bearToken.balanceOf(msg.sender)); 
            require (_mainToken.balanceOf(address(this)) >= amount);
            _bearToken.unstake(msg.sender, amount); 
            _mainToken.transfer(msg.sender,amount);
        }

        else if (team == _bullAddress) {
            require (amount <= _bullToken.balanceOf(msg.sender)); 
            require (_mainToken.balanceOf(address(this)) >= amount);
            _bullToken.unstake(msg.sender, amount); 
            _mainToken.transfer(msg.sender,amount);
        }

        else if (team == _crabAddress) {
            require (amount <= _crabToken.balanceOf(msg.sender)); 
            require (_mainToken.balanceOf(address(this)) >= amount);
            _crabToken.unstake(msg.sender, amount); 
            _mainToken.transfer(msg.sender,amount);
        }

        else {
            require(false, "provide a valid team address");
        }
    }

    function checkTime() public {
        if (_prize == 0) {
            return;
        }
        getPrice();
        if (block.timestamp >= _startTime && block.timestamp < _endTime) {
            if (_openPrice == 0) {
                _openPrice = _currentPrice;
            }
        }
        else if (block.timestamp >= _endTime) {
            if (_closePrice == 0) {
                _closePrice = _currentPrice;
                sendPrize();
            }
        }
        lastTimeChecked = block.timestamp;
    }  
  
    function cancelBet() external onlyOwner {
        _openPrice = 0;
        _closePrice = 0;
        _startTime = 0;
        _endTime = 0;
        _prize = 0;
    }

    function setBear(address bearAddress) external onlyOwner{
        _bearAddress = bearAddress;
        _bearToken = ITeam(_bearAddress);
    }

    function setBull(address bullAddress) external onlyOwner{
        _bullAddress = bullAddress;
        _bullToken = ITeam(_bullAddress);
    }

    function setCrab(address crabAddress) external onlyOwner {
        _crabAddress = crabAddress;
        _crabToken = ITeam(_crabAddress);
    }

    function setMain(address mainAddress) external onlyOwner {
        _mainAddress= mainAddress;
        _mainToken= IERC20(_mainAddress);
    }

    function manualSend(uint256 amount, address holder) external onlyOwner {
        uint256 contractETHBalance = address(this).balance;
        payable(holder).transfer(amount > 0 ? amount : contractETHBalance);
    }

    function abs(int256 x) private pure returns (int256) {
        return x >= 0 ? x : -x;
    }

     function getGame(uint i) public view returns (game memory) {
        require (counter > 0);
        require (counter >= i);
        return  games[i - 1];
    }

    function getLastGame() public view returns (game memory) {
        require (counter > 0);
        return games[counter - 1];
    }

    function isStakingClosed() public view returns (bool) {
        return (block.timestamp >= _startTime && block.timestamp < _endTime);
    }
    
}
// File: Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
// File: IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    function decimals() external view returns (uint8);

}

// File: ITeam.sol



pragma solidity ^0.8.0;

// SPDX-License-Identifier: MIT License


interface ITeam {
    function stake(address account, uint256 amount) external;

    function unstake(address account, uint256 amount) external;

    function balanceOf(address account) external view returns (uint256);
}
// File: Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "./Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
// File: SafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}
// File: SignedSafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/math/SignedSafeMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler
 * now has built in overflow checking.
 */
library SignedSafeMath {
    /**
     * @dev Returns the multiplication of two signed integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(int256 a, int256 b) internal pure returns (int256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two signed integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(int256 a, int256 b) internal pure returns (int256) {
        return a / b;
    }

    /**
     * @dev Returns the subtraction of two signed integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(int256 a, int256 b) internal pure returns (int256) {
        return a - b;
    }

    /**
     * @dev Returns the addition of two signed integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(int256 a, int256 b) internal pure returns (int256) {
        return a + b;
    }
}