// File: InlineBettingFlexible.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 InlineBettingFlexible is Ownable,ReentrancyGuard{
    
    using SafeMath for uint256;
    address public databaseContractAddress=0x96A0F13597D7DAB5952Cdcf8C8Ca09eAc97a0a75;
    uint256 constant secondsInADay=24 hours;
    
    function betFlexible(uint256 amountOfXIV, uint16 typeOfBet, address _betContractAddress, uint256 betSlabeIndex, uint256 _days) 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==1 || typeOfBet==3  || typeOfBet==5 || typeOfBet==7 || typeOfBet==9  || typeOfBet==11, "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");
       
        require(checkTimeForBet(_days),"Staking time closed for the selected day");
        uint256 _currentPrice;
        InlineDatabaseLib.FlexibleInfo memory flexInfo;
        uint256 _coinType;
        if(typeOfBet==1 || typeOfBet==3){
            _coinType=1;
        }else if(typeOfBet==5 || typeOfBet==7){
            _coinType=2;
        }else if(typeOfBet==9 || typeOfBet==11){
            _coinType=3;
        }
        if(typeOfBet==1 || typeOfBet==5 || typeOfBet==9){
            //defi flexible
            require(dContract.getFlexibleMapping(_betContractAddress,_coinType).status,"The currency is currently disabled.");
            require(dContract.getFlexibleDefiCoinArray().length>betSlabeIndex,"Day does not exists.");
            OracleWrapper oWObject=OracleWrapper(dContract.getOracleWrapperContractAddress());
            _currentPrice=uint256(oWObject.getPrice(dContract.getFlexibleMapping(_betContractAddress,_coinType).currencySymbol, dContract.getFlexibleMapping(_betContractAddress,_coinType).oracleType));
            flexInfo=dContract.getFlexibleDefiCoinArray()[betSlabeIndex];
        }else{
            //index flexible
            require(dContract.getFlexibleIndexArray().length>betSlabeIndex,"Day does not exists.");
            _currentPrice=uint256(calculateIndexValueForFlexibleInternal(dContract.getBetId(),_coinType));
            flexInfo=dContract.getFlexibleIndexArray()[betSlabeIndex];
        }
        InlineDatabaseLib.BetInfo memory binfo=InlineDatabaseLib.BetInfo({
            id:uint128(dContract.getBetId()),
            principalAmount:amountOfXIV,
            amount:amountOfXIV,
            userAddress:msg.sender,
            contractAddress:typeOfBet==3?address(0):_betContractAddress,
            betType:typeOfBet,
            currentPrice:_currentPrice,
            betTimePeriod:_days.mul(1 days),
            checkpointPercent:flexInfo.upDownPercentage,
            rewardFactor:flexInfo.rewardFactor,
            riskFactor:flexInfo.riskFactor,
            timestamp:block.timestamp,
            coinType:_coinType,
            status:0
        });
        
        saveBetInfo(binfo);
    }
    function saveBetInfo(InlineDatabaseLib.BetInfo memory binfo) internal{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        dContract.updateBetArray(binfo);
        dContract.updateFindBetInArrayUsingBetIdMapping(dContract.getBetId(),dContract.getBetArray().length.sub(1));
        if(dContract.getBetsAccordingToUserAddress(binfo.userAddress).length==0){
            dContract.addUserAddressUsedForBetting(binfo.userAddress);
        }
        dContract.updateBetAddressesArray(binfo.userAddress,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(),binfo.userAddress,databaseContractAddress,binfo.amount);
        dContract.updateTotalTransactions(dContract.getTotalTransactions().add(binfo.amount));
        dContract.updateExistingBetCheckMapping(binfo.userAddress,binfo.betType,binfo.contractAddress,true);
    }
    function checkTimeForBet(uint256 _days) internal 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 calculateIndexValueForBetActual(uint256 coinType) public view 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));
            }
        }
        return totalMarketcap;
    }
    function calculateIndexValueForBetBase(uint256 coinType) external view returns(uint256){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 totalMarketcap=calculateIndexValueForBetActual(coinType);
         if(dContract.getBetBaseIndexValue(coinType)==0){
            return (10**11);
        }else{
            if(totalMarketcap>dContract.getBetActualIndexValue(coinType)){
                return (dContract.getBetBaseIndexValue(coinType).add((
                                                     (totalMarketcap.sub(dContract.getBetActualIndexValue(coinType)))
                                                     .mul(100*10**8)).div(dContract.getBetActualIndexValue(coinType))));
            }else if(totalMarketcap<dContract.getBetActualIndexValue(coinType)){
                return (dContract.getBetBaseIndexValue(coinType).sub((
                                                     (dContract.getBetActualIndexValue(coinType).sub(totalMarketcap))
                                                     .mul(100*10**8)).div(dContract.getBetActualIndexValue(coinType))));
            }
        }
        return (10**11);
    }
    
    function calculateIndexValueForFlexibleInternal(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 claimBet(uint256 userBetId) external nonReentrant{
        // 0-> defi Fixed, 1->defi flexible, 2-> index Fixed and 3-> index flexible
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 index=dContract.getFindBetInArrayUsingBetIdMapping(userBetId);
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        require((bObject.status==0) 
                || (bObject.status==1)
                || (bObject.status==2),"bet is closed.");
        if(bObject.status==0){
           if(block.timestamp.sub(bObject.timestamp) > 6 days){
                plentyFinal(index,7);
                return;
            }else if(block.timestamp.sub(bObject.timestamp) > 5 days){
                plentyFinal(index,6);
                return;
            }else if(block.timestamp.sub(bObject.timestamp) > 4 days){
                plentyFinal(index,5);
                return;
            }else if(block.timestamp.sub(bObject.timestamp) > 3 days){
                plentyFinal(index,4);
                return;
            }else if(block.timestamp.sub(bObject.timestamp) > 2 days){
                plentyFinal(index,3);
                return;
            }else if(block.timestamp.sub(bObject.timestamp) > 1 days){
                plentyFinal(index,2);
                return;
            }else{
                plentyFinal(index,1);
                return;
            }
        }else{
            claimBetFinal(index);
        }
    }
    
    function claimBetFinal(uint256 index) internal{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        require(bObject.userAddress==msg.sender,"Authentication failure");
        require(bObject.amount!=0,"Your bet amount is 0");
        dContract.transferTokens(dContract.getXIVTokenContractAddress(),msg.sender,(bObject.amount)); 
        bObject.amount=0; // return 3 times
        dContract.updateBetArrayIndex(bObject,index);
    }
    function plentyFinal(uint256 index, uint256 _days) internal{
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        InlineDatabaseLib.BetInfo memory bObject=dContract.getBetArray()[index];
        uint256 plentyPercentage;
        if(bObject.betTimePeriod==1 days){
            plentyPercentage=dContract.getPlentyOneDayPercentage();
        }else if(bObject.betTimePeriod==3 days){
            plentyPercentage=dContract.getPlentyThreeDayPercentage(_days);
        }else if(bObject.betTimePeriod==7 days){
            plentyPercentage=dContract.getPlentySevenDayPercentage(_days);
        }
        if(plentyPercentage!=0){
            uint256 plentyAmount=((plentyPercentage.mul(bObject.amount)).div(10**4));
            uint256 userAmount=(bObject.amount).sub(plentyAmount);
            if(userAmount!=0){
                dContract.transferTokens(dContract.getXIVTokenContractAddress(),msg.sender,userAmount); 
            }
            if(plentyAmount!=0){
                dContract.transferTokens(dContract.getXIVTokenContractAddress(),dContract.getAdminAddress(),plentyAmount); 
            }
            bObject.status=3;
            bObject.amount=0;
            dContract.updateBetArrayIndex(bObject,index);
            dContract.updateExistingBetCheckMapping(bObject.userAddress,bObject.betType,bObject.contractAddress,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 getPieChartValue(uint256 coinType) external view returns(InlineDatabaseLib.IndexCoin[] memory){
        DatabaseContract dContract=DatabaseContract(databaseContractAddress);
        uint256 totalMarketcap;
        InlineDatabaseLib.IndexCoin[] memory tempIndexArray=new InlineDatabaseLib.IndexCoin[](dContract.getAllIndexContractAddressArray(coinType).length);
        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));
            }
        }
        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){
                iCObj.contributionPercentage=(marketCapValue(iCObj,tObj).mul(10**4))/totalMarketcap;
                tempIndexArray[i]=iCObj;
            }
        }
        return tempIndexArray;
    }
    
    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;
    }
}