// File: InlineBettingFixed.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
pragma abicoder v2;

import "./ReentrancyGuard.sol";
import "./Ownable.sol";
import "./SafeMath.sol";
import "./InlineInterface.sol";

contract InlineBettingFixed is Ownable,ReentrancyGuard{
    
    using SafeMath for uint256;
    uint256 constant secondsInADay=24 hours;
    
    uint256 public stakeOffset;
    bool isValued=false;
    address public databaseContractAddress=0x96A0F13597D7DAB5952Cdcf8C8Ca09eAc97a0a75;
    
    
     function betFixed(uint256 amountOfXIV, uint16 typeOfBet, address _betContractAddress, uint256 betSlabeIndex) external nonReentrant{
        /* 0-> defi fixed, 1-> defi flexible, 2-> defi index fixed, 3-> defi index flexible, 
        * 4-> chain coin fixed, 5-> chain coin flexible, 6-> chain index fixed, 7-> chain index flexible
        * 8-> NFT fixed, 9-> NFT flexible, 10-> NFT index fixed, 11-> NFT index flexible
        */
        require(typeOfBet==0 || typeOfBet==2 || typeOfBet==4 || typeOfBet==6 || typeOfBet==8 || typeOfBet==10,"Invalid bet Type");
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        require(!dContract.getExistingBetCheckMapping(msg.sender,typeOfBet,_betContractAddress),"you can't place bet using these values.");
        Token tokenObj = Token(dContract.getXIVTokenContractAddress());
        require((dContract.getBetFactorLP()).mul(dContract.getTokenStakedAmount())>=
                        ((tokenObj.balanceOf(databaseContractAddress)).sub(dContract.getTokenStakedAmount())).add(amountOfXIV),
                        "Staking Vaults Have EXCEEDED CAPACITY. Please Check Back in 24hrs?");
        require(amountOfXIV>=dContract.getMinStakeXIVAmount() && amountOfXIV<=dContract.getMaxStakeXIVAmount(),"Please enter amount in the specified range");
        uint256 _currentPrice;   
        InlineDatabaseLib.FixedInfo memory fixInfo;
        uint256 _coinType;
        if(typeOfBet==0 || typeOfBet==2){
            _coinType=1;
        }else if(typeOfBet==4 || typeOfBet==6){
            _coinType=2;
        }else if(typeOfBet==8 || typeOfBet==10){
            _coinType=3;
        }
        if(typeOfBet==0 || typeOfBet==4 || typeOfBet==8){
            require(dContract.getFixedDefiCoinArray().length>betSlabeIndex,"Day does not exists.");
            require(dContract.getFixedMapping(_betContractAddress,_coinType).status,"The currency is currently disabled.");
            OracleWrapper oWObject=OracleWrapper(dContract.getOracleWrapperContractAddress());
           _currentPrice=uint256(oWObject.getPrice(dContract.getFixedMapping(_betContractAddress,_coinType).currencySymbol, dContract.getFixedMapping(_betContractAddress,_coinType).oracleType));
            fixInfo=dContract.getFixedDefiCoinArray()[betSlabeIndex];
        }else{
            //index Fixed 
            require(dContract.getFixedDefiIndexArray().length>betSlabeIndex,"Day does not exists.");
            _currentPrice=uint256(calculateIndexValueForFixedInternal(dContract.getBetId(),_coinType));
            fixInfo=dContract.getFixedDefiIndexArray()[betSlabeIndex];
        }
        require(checkTimeForBet(fixInfo.daysCount),"Staking time closed for the selected day");
        
         InlineDatabaseLib.BetInfo memory binfo=InlineDatabaseLib.BetInfo({
                id:uint128(dContract.getBetId()),
                principalAmount:amountOfXIV,
                amount:amountOfXIV,
                userAddress:msg.sender,
                contractAddress:typeOfBet==2?address(0):_betContractAddress,
                betType:typeOfBet,
                currentPrice:_currentPrice,
                betTimePeriod:(uint256(fixInfo.daysCount)).mul(1 days),
                checkpointPercent:fixInfo.upDownPercentage,
                rewardFactor:fixInfo.rewardFactor,
                riskFactor:fixInfo.riskFactor,
                timestamp:block.timestamp,
                coinType:_coinType,
                status:0
            });
            dContract.updateBetArray(binfo);
        dContract.updateFindBetInArrayUsingBetIdMapping(dContract.getBetId(),dContract.getBetArray().length.sub(1));
        if(dContract.getBetsAccordingToUserAddress(msg.sender).length==0){
            dContract.addUserAddressUsedForBetting(msg.sender);
        }
        dContract.updateBetAddressesArray(msg.sender,dContract.getBetId());
        dContract.updateBetId(dContract.getBetId().add(1));
        uint256 betEndTime=(((((binfo.timestamp).div(secondsInADay)).mul(secondsInADay))).add(secondsInADay.div(2)).add(binfo.betTimePeriod).sub(1));
        dContract.emitBetDetails(binfo.id,binfo.status,betEndTime);
        
        dContract.transferFromTokens(dContract.getXIVTokenContractAddress(),msg.sender,databaseContractAddress,amountOfXIV);
        dContract.updateTotalTransactions(dContract.getTotalTransactions().add(amountOfXIV));
        dContract.updateExistingBetCheckMapping(msg.sender,typeOfBet,_betContractAddress,true);
    }
    function checkTimeForBet(uint256 _days) public view returns(bool){
        uint256 currentTime=block.timestamp;
        uint256 utcNoon=((currentTime.div(secondsInADay)).mul(secondsInADay)).add(secondsInADay.div(2));
        if(_days==1){
            if(((utcNoon).add(4 hours))>currentTime && utcNoon<currentTime){
                return true;
            }else{
                return false;
            }
        }else if(_days==3){
            if(((utcNoon).add(12 hours))>currentTime && utcNoon<currentTime){
                return true;
            }else{
                return false;
            }
        }
        return true;
    }
    
    function calculateIndexValueForFixedInternal(uint256 _betId,uint256 coinType) internal returns(uint256){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 totalMarketcap;
        for(uint256 i=0;i<dContract.getAllIndexContractAddressArray(coinType).length;i++){
            Token tObj=Token(dContract.getAllIndexContractAddressArray(coinType)[i]);
            InlineDatabaseLib.IndexCoin memory iCObj=dContract.getIndexMapping(dContract.getAllIndexContractAddressArray(coinType)[i],coinType);
            if(iCObj.status){
                totalMarketcap=totalMarketcap.add(marketCapValue(iCObj,tObj));
                dContract.updateBetIndexArray(_betId,iCObj);
            }
        }
        InlineDatabaseLib.BetPriceHistory memory bPHObj=InlineDatabaseLib.BetPriceHistory({
            baseIndexValue:uint128(dContract.getBetBaseIndexValue(coinType)==0?10**11:dContract.getBetBaseIndexValue(coinType)),
            actualIndexValue:uint128(totalMarketcap)
        });
        dContract.updateBetPriceHistoryMapping(_betId,bPHObj);
        if(dContract.getBetBaseIndexValue(coinType)==0){
            dContract.updateBetBaseIndexValue(10**11,coinType);
        }else{
            if(totalMarketcap>dContract.getBetActualIndexValue(coinType)){
                dContract.updateBetBaseIndexValue(dContract.getBetBaseIndexValue(coinType).add((
                                                     (totalMarketcap.sub(dContract.getBetActualIndexValue(coinType)))
                                                     .mul(100*10**8)).div(dContract.getBetActualIndexValue(coinType))),coinType);
            }else if(totalMarketcap<dContract.getBetActualIndexValue(coinType)){
                dContract.updateBetBaseIndexValue(dContract.getBetBaseIndexValue(coinType).sub((
                                                     (dContract.getBetActualIndexValue(coinType).sub(totalMarketcap))
                                                     .mul(100*10**8)).div(dContract.getBetActualIndexValue(coinType))),coinType);
            }
        }
        dContract.updateBetActualIndexValue(totalMarketcap,coinType);
        return totalMarketcap;
    }
    function updateStatus(uint256[] memory offerIds) external nonReentrant{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        OracleWrapper oWObject=OracleWrapper(dContract.getOracleWrapperContractAddress());
          for(uint256 i=0;i<offerIds.length;i++){ 
            InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[offerIds[i]];
            if(bObject.status==0){
                uint256 sevenDaysTime=(((((bObject.timestamp).div(secondsInADay)).mul(secondsInADay))).add(secondsInADay.div(2)).add(bObject.betTimePeriod).sub(1));
                if(block.timestamp>=sevenDaysTime){
                    if(!isValued){
                        stakeOffset=stakeOffset.add(dContract.getTokenStakedAmount());
                        isValued=true;
                    }
                     if(bObject.betType==0 || bObject.betType==1 || bObject.betType==4 || bObject.betType==5 || bObject.betType==8 || bObject.betType==9){
                        // defi fixed
                        string memory tempSymbol;
                        uint256 tempOracle;
                        if(bObject.betType==0 || bObject.betType==4 || bObject.betType==8){
                            tempSymbol=dContract.getFixedMapping(bObject.contractAddress,bObject.coinType).currencySymbol;
                            tempOracle=dContract.getFixedMapping(bObject.contractAddress,bObject.coinType).oracleType;
                        }else{
                            tempSymbol=dContract.getFlexibleMapping(bObject.contractAddress,bObject.coinType).currencySymbol;
                            tempOracle=dContract.getFlexibleMapping(bObject.contractAddress,bObject.coinType).oracleType;
                        }
                        uint256 currentprice=uint256(oWObject.getPrice(tempSymbol, tempOracle));
                       
                        if(currentprice>bObject.currentPrice){
                            uint16 percentageValue=uint16(((currentprice.sub(bObject.currentPrice)).mul(10**4))
                                                    .div(currentprice));
                            if(percentageValue>=bObject.checkpointPercent){
                                updateXIVForStakers(offerIds[i], true);
                            }else{
                                updateXIVForStakers(offerIds[i], false);
                            }
                        }else{
                            updateXIVForStakers(offerIds[i], false);
                        }
                    }else{
                        //index  
                      updateXIVForStakersIndex(offerIds[i]);
                        
                    }
                }
            }
        }
    }
    function getUserStakedAddressCount() public view returns(uint256){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        return dContract.getUserStakedAddress().length;
    }
    
    function incentiveStakers(uint256 pageNo, uint256 pageSize) external nonReentrant{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 pageStart=pageNo.mul(pageSize);
        uint256 pageSizeValue=(pageSize.mul(pageNo.add(1)));
        if(getUserStakedAddressCount()<pageSizeValue){
            pageSizeValue=getUserStakedAddressCount();
        }
        for(uint256 i=pageStart;i<pageSizeValue;i++){
            address userAddress=dContract.getUserStakedAddress()[i];
            uint256 updatedAmount;
            if(stakeOffset>0){
                updatedAmount=(((dContract.getTokensStaked(userAddress).mul(10**4).mul(stakeOffset))
                                    .div(dContract.getTokenStakedAmount().mul(10**4))));
            }else{
                updatedAmount=dContract.getTokensStaked(userAddress);
            }
            
            dContract.updateTokensStaked(userAddress,updatedAmount);
        }
        if(getUserStakedAddressCount()<pageSizeValue || getUserStakedAddressCount()==pageSizeValue){
            if(stakeOffset>0){
                dContract.updateTokenStakedAmount(stakeOffset);
            }else{
                dContract.updateTokenStakedAmount(dContract.getTokenStakedAmount());
            }
            stakeOffset=0;
            isValued=false;
        }
    }
    function updateXIVForStakers(uint256 index, bool isWon) internal{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        if(isWon){
            bObject.status=1;
            uint256 rewardAmount=(uint256(bObject.rewardFactor).mul(bObject.amount)).div(10**4);
            stakeOffset=stakeOffset.sub(rewardAmount);
            bObject.amount=bObject.amount.add(rewardAmount);
            dContract.updateBetArrayIndex(bObject,index);
        }else{
            bObject.status=2;
            uint256 riskAmount=(uint256(bObject.riskFactor).mul(bObject.amount)).div(10**4);
            stakeOffset=stakeOffset.add(riskAmount);
            bObject.amount=bObject.amount.sub(riskAmount);
            dContract.updateBetArrayIndex(bObject,index);
        }
        dContract.updateExistingBetCheckMapping(bObject.userAddress,bObject.betType,bObject.contractAddress,false);
    }
    
    function getCalculateIndexValue(uint256 index) public view returns(uint256){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 totalMarketcap;
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        for(uint256 i=0;i<dContract.getBetIndexArray(bObject.id).length;i++){
            Token tObj=Token(dContract.getBetIndexArray(bObject.id)[i].contractAddress);
            InlineDatabaseLib.IndexCoin memory iCObj=dContract.getIndexMapping(dContract.getBetIndexArray(bObject.id)[i].contractAddress,bObject.coinType);
            if(iCObj.status){
                totalMarketcap=totalMarketcap.add(marketCapValue(iCObj,tObj));
            }
        }
        return totalMarketcap;
    }
    
    function updateXIVForStakersIndex(uint256 index) internal{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 totalMarketcap=getCalculateIndexValue(index);
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        if(dContract.getBetPriceHistoryMapping(bObject.id).actualIndexValue<totalMarketcap){
             uint16 percentageValue=uint16(((uint256(totalMarketcap)
                                                .sub(dContract.getBetPriceHistoryMapping(bObject.id).actualIndexValue)
                                                .mul(10**4)).div(totalMarketcap)));
            if(percentageValue>=bObject.checkpointPercent){
                updateXIVForStakers(index, true);
            }else{
                updateXIVForStakers(index, false);
            }
        }else{
            updateXIVForStakers(index, false);
        }
    }
    function marketCapValue(InlineDatabaseLib.IndexCoin memory iCObj,Token tObj) internal view returns(uint256){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        OracleWrapper oWObject=OracleWrapper(dContract.getOracleWrapperContractAddress());
         if((keccak256(abi.encodePacked(iCObj.currencySymbol))) == (keccak256(abi.encodePacked("ETH"))) || (keccak256(abi.encodePacked(iCObj.currencySymbol))) == (keccak256(abi.encodePacked("BTC")))){
            return ((((oWObject.getPrice(iCObj.currencySymbol,iCObj.oracleType))
                                    /*    .mul(iCObj.contributionPercentage)*/)
                                        .div(10**2)));
        }else{
            return (((tObj.totalSupply().mul(oWObject.getPrice(iCObj.currencySymbol,iCObj.oracleType))
                                /*.mul(iCObj.contributionPercentage)*/)
                                .div((10**tObj.decimals()).mul(10**2))));
        }
    }
    
    function updateDatabaseAddress(address _databaseContractAddress) external onlyOwner{
        databaseContractAddress=_databaseContractAddress;
    }
}

// File: InlineDatabaseLib.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
pragma abicoder v2;

library InlineDatabaseLib{
    // deficoin struct for deficoinmappings..
   struct DefiCoin{
        uint16 oracleType;
        bool status;
        string currencySymbol;
    }
    struct TimePeriod{
        bool status;
        uint64 _days;
    }
     struct FlexibleInfo{
        uint128 id;
        uint16 upDownPercentage; //10**2
        uint16 riskFactor;       //10**2
        uint16 rewardFactor;     //10**2
        bool status;
    }
    struct FixedInfo{
        uint128 id;
        uint64 daysCount;// integer value
        uint16 upDownPercentage; //10**2
        uint16 riskFactor;       //10**2
        uint16 rewardFactor;     //10**2
        bool status;
    }
    struct IndexCoin{
        uint16 oracleType;
        address contractAddress;
        bool status;
        string currencySymbol;
        uint256 contributionPercentage; //10**2
    }
    struct BetPriceHistory{
        uint128 baseIndexValue;
        uint128 actualIndexValue;
    }
    struct LPLockedInfo{
        uint256 lockedTimeStamp;
        uint256 amountLocked;
    }
    struct BetInfo{
        uint256 coinType;
        uint256 principalAmount;
        uint256 currentPrice;
        uint256 timestamp;
        uint256 betTimePeriod;
        uint256 amount;
        address userAddress;
        address contractAddress;
        uint128 id;
        uint16 betType; //
        uint16 checkpointPercent;
        uint16 rewardFactor;
        uint16 riskFactor;
        uint16 status; // 0->bet active, 1->bet won, 2->bet lost, 3-> withdraw before result
    }
}


// File: InlineInterface.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
pragma abicoder v2;

import "./InlineDatabaseLib.sol";

interface Token{
    function decimals() external view returns(uint256);
    function symbol() external view returns(string memory);
    function totalSupply() external view returns (uint256);
    function balanceOf(address who) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
}


interface OracleWrapper{
    function getPrice(string calldata currencySymbol,uint256 oracleType) external view returns (uint256);
}
interface DatabaseContract{
    function transferTokens(address contractAddress,address userAddress,uint256 amount) external;
    function transferFromTokens(address contractAddress,address fromAddress, address toAddress,uint256 amount) external;
    function getTokensStaked(address userAddress) external view returns(uint256);
    function updateTokensStaked(address userAddress, uint256 amount) external;
    function getTokenStakedAmount() external view returns(uint256);
    function updateTokenStakedAmount(uint256 _tokenStakedAmount) external;
    function getBetId() external view returns(uint256);
    function updateBetId(uint256 _userBetId) external;
    function updateBetArray(InlineDatabaseLib.BetInfo memory bObject) external;
    function getBetArray() external view returns(InlineDatabaseLib.BetInfo[] memory);
    function getFindBetInArrayUsingBetIdMapping(uint256 _betid) external view returns(uint256);
    function updateFindBetInArrayUsingBetIdMapping(uint256 _betid, uint256 value) external;
    function updateUserStakedAddress(address _address) external;
    function updateUserStakedAddress(address[] memory _userStakedAddress) external;
    function getUserStakedAddress() external view returns(address[] memory);
    function getFixedMapping(address _betContractAddress, uint256 coinType) external view returns(InlineDatabaseLib.DefiCoin memory);
    function getFlexibleMapping(address _betContractAddress, uint256 coinType) external view returns(InlineDatabaseLib.DefiCoin memory);
    function getFlexibleDefiCoinArray() external view returns(InlineDatabaseLib.FlexibleInfo[] memory);
    function getFlexibleIndexArray() external view returns(InlineDatabaseLib.FlexibleInfo[] memory);
    function updateBetArrayIndex(InlineDatabaseLib.BetInfo memory bObject, uint256 index) external;
    function updateBetIndexArray(uint256 _betId, InlineDatabaseLib.IndexCoin memory iCArray) external;
    function updateBetBaseIndexValue(uint256 _betBaseIndexValue, uint256 coinType) external;
    function getBetBaseIndexValue(uint256 coinType) external view returns(uint256);
    function updateBetPriceHistoryMapping(uint256 _betId, InlineDatabaseLib.BetPriceHistory memory bPHObj) external;
    function updateBetActualIndexValue(uint256 _betActualIndexValue, uint256 coinType) external;
    function getBetActualIndexValue(uint256 coinType) external view returns(uint256);
    function getBetIndexArray(uint256 _betId) external view returns(InlineDatabaseLib.IndexCoin[] memory);
    function getBetPriceHistoryMapping(uint256 _betId) external view returns(InlineDatabaseLib.BetPriceHistory memory);
    function getXIVTokenContractAddress() external view returns(address);
    function getAllIndexContractAddressArray(uint256 coinType) external view returns(address[] memory);
    function getIndexMapping(address _ContractAddress, uint256 coinType) external view returns(InlineDatabaseLib.IndexCoin memory);
    
    function getOracleWrapperContractAddress() external view returns(address);
    function getPlentyOneDayPercentage() external view returns(uint256);
    function getPlentyThreeDayPercentage(uint256 _days) external view returns(uint256);
    function getPlentySevenDayPercentage(uint256 _days) external view returns(uint256);
    function getBetsAccordingToUserAddress(address userAddress) external view returns(uint256[] memory);
    function updateBetAddressesArray(address userAddress, uint256 _betId) external;
    function addUserAddressUsedForBetting(address userAddress) external;
    function getUserAddressUsedForBetting() external view returns(address[] memory);
    function getFixedDefiCoinArray() external view returns(InlineDatabaseLib.FixedInfo[] memory);
    function getFixedDefiIndexArray() external view returns(InlineDatabaseLib.FixedInfo[] memory);
    function getMaxStakeXIVAmount() external view returns(uint256);
    function getMinStakeXIVAmount() external view returns(uint256);
    function getBetFactorLP() external view returns(uint256);
    function updateActualAmountStakedByUser(address userAddress, uint256 amount) external;
    function getActualAmountStakedByUser(address userAddress) external view returns(uint256);
    function isDaysAvailable(uint256 _days) external view returns(bool);
    function updateExistingBetCheckMapping(address _userAddress,uint256 _betType, address _BetContractAddress,bool status) external;
    function getExistingBetCheckMapping(address _userAddress,uint256 _betType, address _BetContractAddress) external view returns(bool);
    function updateTotalTransactions(uint256 _totalTransactions) external;
    function getTotalTransactions() external view returns(uint256);
    function getFlexibleDefiCoinTimePeriodArray() external view returns(InlineDatabaseLib.TimePeriod[] memory);
    function getFlexibleIndexTimePeriodArray() external view returns(InlineDatabaseLib.TimePeriod[] memory);
    function getMinLPvalue() external view returns(uint256);
    function getLockingPeriodForLPMapping(address userAddress) external view returns(InlineDatabaseLib.LPLockedInfo memory);
    function updateLockingPeriodForLPMapping(address userAddress, uint256 _amountLocked, uint256 _lockedTimeStamp) external;
    function emitBetDetails(uint256  betId, uint256  status, uint256  betEndTime) external;
    function emitLPEvent(uint256 typeOfLP, address userAddress, uint256 amount, uint256 timestamp) external ;
    function updateIsStakeMapping(address userAddress,bool isStake) external;
    function getIsStakeMapping(address userAddress) external view returns(bool);
    function getAdminAddress() external view returns(address);
    function getMaxLPLimit() external view returns(uint256);
}

// File: Ownable.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

contract Ownable {

    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor(){
        _setOwner(msg.sender);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0));
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
    
    function _setOwner(address newOwner) internal {
        owner = newOwner;
    }
}

// File: ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}


// File: SafeMath.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @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) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @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 sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @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) {
        // 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 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts 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) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts 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) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts 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 mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}