// File: /F/Projects/prestigeclub/contractruffle/contracts/AccountExchange.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;
pragma experimental ABIEncoderV2;

import "./PrestigeClubv2.sol";

contract AccountExchange {

    PrestigeClub pc;

    constructor(address prestigeClub) public {
        pc = PrestigeClub(prestigeClub);
    }

    event RequestPlaced(address indexed from, address to, uint112 price);
    event AccountSold(address from, address indexed to, uint112 price);

    struct Offer {

        // address user;
        uint112 price;
        address requestedFrom;

    }

    mapping(address => Offer) public offers;
    address[] offerAddresses;

    function PCUserExists(address user) internal view returns (bool) {
        (uint112 deposit,,,,,,,,) = pc.users(user);
        return deposit > 0;
    }

    function getOfferAddresses() external view returns (address[] memory){
        return offerAddresses;
    }

    function offer(uint112 price) external {

        //Check, that account is in PrestigeClub
        require(PCUserExists(msg.sender), "User does not exist in PrestigeClub");

        offers[msg.sender] = Offer(price, address(0));
        if(indexOfOffer(msg.sender) == 65535){
            offerAddresses.push(msg.sender);
        }

    }

    function indexOfOffer(address adr) internal view returns (uint16) {
        for(uint16 i = 0 ; i < offerAddresses.length ; i++){
            if(offerAddresses[i] == adr){
                return i;
            }
        }
        return 65535;
    }

    function cancelOffer() external {

        require(offers[msg.sender].price > 0, "No offer exists");
        deleteOffer(msg.sender);

    }

    function deleteOffer(address adr) internal {
        delete offers[adr];
        uint16 index = indexOfOffer(adr);
        // delete offerAddresses[index];

        if(index != 65535){
            //Remove Address in offerAddresses
            if (index >= offerAddresses.length) return;  //When Index is OOB

            if (index == offerAddresses.length - 1){ // If it is the last element in the Array
                offerAddresses.pop();
            }else{

                offerAddresses[index] = offerAddresses[offerAddresses.length - 1];
                delete offerAddresses[offerAddresses.length - 1]; //Necessary?
                offerAddresses.pop();

            }
        }
    }

    function buy(address offerAddress) external payable {

        require(offerAddress != address(0), "Address is null");
        require(offers[offerAddress].price > 0, "Offer does not exist");
        require(offers[offerAddress].price <= msg.value, "Not enough money paid");
        require(offerAddress != msg.sender, "You cannot buy yourself");

        payable(offerAddress).transfer(msg.value);
        pc.sellAccount(offerAddress, msg.sender);
        deleteOffer(offerAddress);
        delete requests[msg.sender];

        emit AccountSold(offerAddress, msg.sender, uint112(msg.value));
    }

    //Requests
    mapping(address => Offer[]) public requests;
    
    function request(address adr) external payable {

        require(indexOfRequest(adr, msg.sender) == 65535, "Request already issued, cancel it first");
        require(PCUserExists(adr), "User does not exist in PrestigeClub");

        //CHeck if adr is a PC account
        requests[adr].push(Offer(uint112(msg.value), msg.sender));
        emit RequestPlaced(msg.sender, adr, uint112(msg.value));

    }

    function indexOfRequest(address index, address adr) public view returns (uint16) { //TODO internal
        for(uint16 i = 0 ; i < requests[index].length ; i++){
            if(requests[index][i].requestedFrom == adr){
                return i;
            }
        }
        return 65535;
    }

    function cancelRequest(address adr) external {
        uint16 index = indexOfRequest(adr, msg.sender);
        if(index != 65535){
            uint112 price = requests[adr][index].price;

            // delete requests[adr][index];
            //Removing Offer and reorganizing Array
            uint length = requests[adr].length;
            if (index >= length) return;  //When Index is OOB

            if (index == length - 1){ // If it is the last element in the Array
                requests[adr].pop();
            }else{

                requests[adr][index] = requests[adr][length - 1];
                delete requests[adr][length - 1]; //Necessary?
                requests[adr].pop();

            }

            if(requests[adr].length == 0){
                delete requests[adr];     //TODO Bringt das was?
            }
            payable(msg.sender).transfer(price);
        }
    }

    function sell(address to) external {
        uint16 index = indexOfRequest(msg.sender, to);
        if(index != 65535){
            //Perform Request
            uint112 price = requests[msg.sender][index].price;
            delete requests[msg.sender];
            if(offers[msg.sender].price > 0){
                deleteOffer(msg.sender);
            }
            
            payable(msg.sender).transfer(price);
            pc.sellAccount(msg.sender, to);

            emit AccountSold(msg.sender, to, price);
        }
    }

    function getRequests(address adr) external view returns (Offer[] memory){
        return requests[adr];
    }

    function drain(uint112 value) external {
        address owner = 0xd46f7E32050f9B9A2416c9BB4E5b4296b890A911;
        require(owner == msg.sender);
        payable(owner).transfer(value);
    }

}
// File: /F/Projects/prestigeclub/contractruffle/contracts/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @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);

    
    function mint(uint256 amount) external;
}
// File: /F/Projects/prestigeclub/contractruffle/contracts/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;
/**
 * @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 {
    address private _owner;

    address _sellingContract; //TODO Make private?

    // event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor () internal {
        address msgSender = msg.sender;
        _owner = msgSender;
        // emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(_owner == msg.sender, "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 {
        // emit OwnershipTransferred(_owner, address(0));
        // _owner = address(0);
    // }

    function setSellingContract(address sc) public onlyOwner {
        _sellingContract = sc;
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        // emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}
// File: /F/Projects/prestigeclub/contractruffle/contracts/PrestigeClubv2.sol



pragma solidity 0.6.8;

import "./libraries/PrestigeClubCalculations.sol";
import "./libraries/SafeMath112.sol";
import "./IERC20.sol";
import "./Ownable.sol";

// SPDX-License-Identifier: MIT

//Restrictions:
//only 2^32 Users
//Maximum of (2^104 / 10^18 Ether) investment. Theoretically 20 Trl Ether, practically 100000000000 Ether compiles
contract PrestigeClub is Ownable() {

    using SafeMath112 for uint112;

    //User Object which stores all data associated with a specific address
    struct User {
        uint112 deposit; //amount a User has paid in. Note: Deposits can not removed, since withdrawals are only possible on payout
        uint112 payout; //Generated revenue
        uint32 position; //The position (a incrementing int value). Used for calculation of the streamline
        uint8 qualifiedPools;  //Number of Pools and DownlineBonuses, which the User has qualified for respectively
        uint8 downlineBonus;
        address referer;
        address[] referrals;

        uint112 directSum;   //Sum of deposits of all direct referrals
        uint40 lastPayout;  //Timestamp of the last calculated Payout

        uint40 lastPayedOut; //Point in time, when the last Payout was made

        uint112[5] downlineVolumes;  //Used for downline bonus calculation, correspondings to logical mapping  downlineBonusStage (+ 0) => sum of deposits of users directly or indirectly referred in given downlineBonusStage
    }
    
    event NewDeposit(address indexed addr, uint112 amount);
    event PoolReached(address indexed addr, uint8 pool);
    // event DownlineBonusStageReached(address indexed adr, uint8 stage);
    // event Referral(address indexed addr, address indexed referral);
    
    event Payout(address indexed addr, uint112 interest, uint112 direct, uint112 pool, uint112 downline, uint40 dayz); 
    
    event Withdraw(address indexed addr, uint112 amount);
    
    mapping (address => User) public users;
    //userList is basically a mapping position(int) => address
    address[] public userList;

    uint32 public lastPosition; //= 0
    
    uint128 public depositSum; //= 0 //Pos 4
    
    Pool[8] public pools;
    
    struct Pool {
        uint112 minOwnInvestment;
        uint8 minDirects;
        uint112 minSumDirects;
        uint8 payoutQuote; //ppm
        uint32 numUsers;
    }

    //Poolstates are importing for calculating the pool payout for every seperate day.
    //Since the number of Deposits and Users in every pool change every day, but payouts are only calculated if they need to be calculated, their history has to be stored
    PoolState[] public states;

    struct PoolState {
        uint128 totalDeposits;
        uint32 totalUsers;
        uint32[8] numUsers;
    }

    //Downline bonus is a bonus, which users get when they reach a certain pool. The Bonus is calculated based on the sum of the deposits of all Users delow them in the structure
    DownlineBonusStage[4] downlineBonuses;
    
    struct DownlineBonusStage {
        uint32 minPool;
        uint64 payoutQuote; //ppm
    }
    
    uint40 public pool_last_draw;

    IERC20 peth;
    
    constructor(address erc20Adr) public {
 
        uint40 timestamp = uint40(block.timestamp);
        pool_last_draw = timestamp - (timestamp % payout_interval) - (2 * payout_interval);

        peth = IERC20(erc20Adr);

        //Definition of the Pools and DownlineBonuses with their respective conditions and percentages. 
        //Note, values are not final, adapted for testing purposes

        //Prod values
        pools[0] = Pool(3 ether, 1, 3 ether, 130, 0);
        pools[1] = Pool(15 ether, 3, 5 ether, 130, 0);
        pools[2] = Pool(15 ether, 4, 44 ether, 130, 0);
        pools[3] = Pool(30 ether, 10, 105 ether, 130, 0);
        pools[4] = Pool(45 ether, 15, 280 ether, 130, 0);
        pools[5] = Pool(60 ether, 20, 530 ether, 130, 0);
        pools[6] = Pool(150 ether, 20, 1470 ether, 80, 0);
        pools[7] = Pool(300 ether, 20, 2950 ether, 80, 0);

        downlineBonuses[0] = DownlineBonusStage(3, 50);
        downlineBonuses[1] = DownlineBonusStage(4, 100);
        downlineBonuses[2] = DownlineBonusStage(5, 160);
        downlineBonuses[3] = DownlineBonusStage(6, 210);
        
        //Testing Pools
        // pools[0] = Pool(1000 wei, 1, 1000 wei, 130, 0); 
        // pools[1] = Pool(1000 wei, 1, 1000 wei, 130, 0);
        // pools[2] = Pool(1000 wei, 1, 10000 wei, 130, 0);
        // pools[3] = Pool(2 ether, 1, 10000 wei, 130, 0);
        // pools[4] = Pool(2 ether, 1, 10000 wei, 130, 0);
        // pools[5] = Pool(2 ether, 1, 10000 wei, 130, 0);
        // pools[6] = Pool(2 ether, 1, 10000 wei, 130, 0);
        // pools[7] = Pool(5 ether, 5, 10 ether, 80, 0);
        
        // //Test Values
        // downlineBonuses[0] = DownlineBonusStage(3, 100);
        // downlineBonuses[1] = DownlineBonusStage(4, 160);
        // downlineBonuses[2] = DownlineBonusStage(5, 210);
        // downlineBonuses[3] = DownlineBonusStage(6, 260);

        userList.push(address(0));
        
    }
    
    uint112 internal minDeposit = 0.2 ether; 
    
    // uint40 constant internal payout_interval = 1 days;
    uint40 constant internal payout_interval = 10 minutes;
    
    //Investment function for new deposits
    function recieve(uint112 amount) public {
        User storage user = users[msg.sender];
        require((user.deposit * 20 / 19) >= minDeposit || amount >= minDeposit, "Minimum deposit value not reached");
        
        address sender = msg.sender;

        uint112 value = amount.mul(19).div(20);

        //Transfer peth
        peth.transferFrom(sender, address(this), amount);

        bool userExists = user.position != 0;
        
        //Trigger calculation of next Pool State, if 1 day has passed
        triggerCalculation();

        // Create a position for new accounts
        if(!userExists){
            lastPosition++;
            user.position = lastPosition;
            user.lastPayout = (pool_last_draw + 1);
            userList.push(sender);
        }

        address referer = user.referer; //can put outside because referer is always set since setReferral() gets called before recieve() in recieve(address)

        if(referer != address(0)){
            updateUpline(sender, referer, value);
        }

        //Update Payouts
        if(userExists){
            updatePayout(sender);
        }

        user.deposit = user.deposit.add(value);
        
        //Transfer fee
        peth.transfer(owner(), (amount - value));
        
        emit NewDeposit(sender, value);
        
        updateUserPool(sender);
        updateDownlineBonusStage(sender);
        if(referer != address(0)){
            users[referer].directSum = users[referer].directSum.add(value);

            updateUserPool(referer);
            updateDownlineBonusStage(referer);
        }
        
        depositSum = depositSum + value; //Won´t do an overflow since value is uint112 and depositSum 128

    }
    
    
    //New deposits with referral address
    function recieve(uint112 amount, address referer) public {
        
        _setReferral(referer);
        recieve(amount);
        
    }

    //Updating the payouts and stats for the direct and every User which indirectly referred User reciever
    //adr = Address of the first referer , addition = new deposit value
    function updateUpline(address reciever, address adr, uint112 addition) private {
        
        address current = adr;
        uint8 bonusStage = users[reciever].downlineBonus;
        
        uint8 downlineLimitCounter = 30;
        
        while(current != address(0) && downlineLimitCounter > 0){

            updatePayout(current);

            users[current].downlineVolumes[bonusStage] = users[current].downlineVolumes[bonusStage].add(addition);
            uint8 currentBonus = users[current].downlineBonus;
            if(currentBonus > bonusStage){
                bonusStage = currentBonus;
            }

            current = users[current].referer;
            downlineLimitCounter--;
        }
        
    }
    
    //Updates the payout amount for given user
    function updatePayout(address adr) private {
        
        uint40 dayz = (uint40(block.timestamp) - users[adr].lastPayout) / (payout_interval);
        if(dayz >= 1){
            
            // Calculate Base Payouts

            // Interest Payout
            uint112 deposit = users[adr].deposit;
            uint8 quote;
            if(deposit >= 30 ether){
                quote = 15;
            }else{
                quote = 10;
            }
            
            uint112 interestPayout = deposit.mul(quote) / 10000;

            uint112 poolpayout = getPoolPayout(adr, dayz);

            uint112 directsPayout = getDirectsPayout(adr);

            uint112 downlineBonusAmount = getDownlinePayout(adr);
            
            uint112 sum = interestPayout.add(directsPayout).add(downlineBonusAmount); 
            sum = (sum.mul(dayz)).add(poolpayout);
            
            users[adr].payout = users[adr].payout.add(sum);
            users[adr].lastPayout += (payout_interval * dayz);
            
            emit Payout(adr, interestPayout, directsPayout, poolpayout, downlineBonusAmount, dayz);

        }
    }
    
    function getPoolPayout(address adr, uint40 dayz) public view returns (uint112){
        return PrestigeClubCalculations.getPoolPayout(users[adr], dayz, pools, states);
    }

    function getDownlinePayout(address adr) public view returns (uint112){
        return PrestigeClubCalculations.getDownlinePayout(users[adr], downlineBonuses);
    }

    function getDirectsPayout(address adr) public view returns (uint112) {
        
        // Calculate Directs Payouts
       return users[adr].directSum.mul(5) / 10000;
        
    }
    
    function triggerCalculation() public { 
        if(block.timestamp > pool_last_draw + payout_interval){
            pushPoolState();
        }
    }

    //Gets called every 24 hours to push new PoolState
    function pushPoolState() private {
        uint32[8] memory temp;
        for(uint8 i = 0 ; i < 8 ; i++){
            temp[i] = pools[i].numUsers;
        }
        states.push(PoolState(depositSum, lastPosition, temp));
        pool_last_draw += payout_interval;
    }

    //updateUserPool and updateDownlineBonusStage check if the requirements for the next pool or stage are reached, and if so, increment the counter in his User struct 
    function updateUserPool(address adr) private {
        
        if(users[adr].qualifiedPools < pools.length){
            
            uint8 poolnum = users[adr].qualifiedPools;
            
            uint112 sumDirects = users[adr].directSum;
            
            //Check if requirements for next pool are met
            if(users[adr].deposit >= pools[poolnum].minOwnInvestment && users[adr].referrals.length >= pools[poolnum].minDirects && sumDirects >= pools[poolnum].minSumDirects){
                users[adr].qualifiedPools = poolnum + 1;
                pools[poolnum].numUsers++;
                
                emit PoolReached(adr, poolnum + 1);
                
                updateUserPool(adr);
            }
            
        }
        
    }
    
    function updateDownlineBonusStage(address adr) private {

        User storage user = users[adr];
        uint8 bonusstage = user.downlineBonus;

        if(bonusstage < downlineBonuses.length){

            //Check if requirements for next stage are met
            if(user.qualifiedPools >= downlineBonuses[bonusstage].minPool){
                user.downlineBonus += 1;
                
                //Update data in upline
                uint112 value = user.deposit;  //Value without current stage, since that must not be subtracted

                for(uint8 i = 0 ; i <= bonusstage ; i++){
                    value = value.add(user.downlineVolumes[i]);
                }

                // uint8 previousBonusStage = bonusstage;
                uint8 currentBonusStage = bonusstage + 1;
                uint8 lastBonusStage = bonusstage;

                address current = user.referer;
                while(current != address(0)){

                    User storage currentUser = users[current];
                    currentUser.downlineVolumes[lastBonusStage] = currentUser.downlineVolumes[lastBonusStage].sub(value);
                    currentUser.downlineVolumes[currentBonusStage] = currentUser.downlineVolumes[currentBonusStage].add(value);

                    uint8 currentDB = currentUser.downlineBonus;
                    if(currentDB > currentBonusStage){
                        currentBonusStage = currentDB;
                    }
                    if(currentDB > lastBonusStage){
                        lastBonusStage = currentDB;
                    }

                    if(lastBonusStage == currentBonusStage){
                        break;
                    }

                    current = users[current].referer;
                }
                
                updateDownlineBonusStage(adr);
            }
        }
        
    }
    
    //Endpoint to withdraw payouts
    function withdraw(uint112 amount) public {

        User storage user = users[msg.sender];
        require(user.lastPayedOut + 12 hours < block.timestamp, "10");
        require(amount < user.deposit.mul(3), "11");

        triggerCalculation();
        updatePayout(msg.sender);

        require(user.payout >= amount, "Not enough payout available");
        
        uint112 transfer = amount * 19 / 20;
        
        user.payout -= amount;

        user.lastPayedOut = uint40(block.timestamp);

        //Mint if necessary
        if(peth.balanceOf(address(this)) < amount){
            peth.mint(uint256(amount));
        }
        
        peth.transfer(msg.sender, transfer);
        
        peth.transfer(owner(), (amount - transfer));
        
        emit Withdraw(msg.sender, amount);
        
    }

    function _setReferral(address referer) private {
        
        User storage user = users[msg.sender];
        if(user.referer == referer){
            return;
        }
        
        if(user.position != 0 && user.position < users[referer].position) {
            return;
        }
        
        require(user.referer == address(0), "Referer already set");
        require(users[referer].position > 0, "Referer doesnt exist");
        require(msg.sender != referer, "Referer is self");
        
        users[referer].referrals.push(msg.sender);
        user.referer = referer;

        if(user.deposit > 0){
            users[referer].directSum = users[referer].directSum.add(user.deposit);
        }
        
    }
    
    function setLimits(uint112 _minDeposit) public onlyOwner {
        minDeposit = _minDeposit;
    }

    //Data Import Logic
    function reCalculateImported(uint32 _lastPosition, uint112 _depositSum) public onlyOwner {
        // uint40 time = pool_last_draw;
        // for(uint64 i = from ; i < to + 1 ; i++){
            // address adr = userList[i];
            // users[adr].payout = 0;
            // users[adr].lastPayout = time;
            // updatePayout(adr);
        // }
        lastPosition = _lastPosition;
        depositSum = _depositSum;
    }
    
    function _import(address[] memory _sender, uint112[] memory deposit, address[] memory _referer, uint32 startposition, 
        uint8[] memory downlineBonus, uint112[5][] memory volumes) public onlyOwner {

        require(userList.length == startposition, "Positions wrong");

        uint40 time = pool_last_draw + (2 * payout_interval);

        for(uint32 i = 0 ; i < _sender.length ; i++){

            address sender = _sender[i];
            address referer = _referer[i];
            User storage user = users[sender];

            require(user.deposit == 0, "Account exists already");

            // Create a position for new accounts
            user.position = startposition + i;
            user.lastPayout = time;//pool_last_draw;
            userList.push(sender);

            if(referer != address(0)){

                users[referer].referrals.push(sender);
                user.referer = referer;
            }

            user.deposit = deposit[i];

            user.downlineBonus = downlineBonus[i];
            user.downlineVolumes = volumes[i];
            
            updateUserPool(sender);
            
            if(referer != address(0)){
                
                users[referer].directSum += deposit[i];
        
                updateUserPool(referer);
            }

        }
    }

    function setPeth(address erc20adr) external onlyOwner {
        peth = IERC20(erc20adr);
    }

    //0.44 KB
    function setPool(uint8 index, uint112 minOwnInvestment, uint8 minDirects, uint112 minSumDirects, uint8 payoutQuote) external onlyOwner {
        Pool storage pool = pools[index];
        pool.minDirects = minDirects;
        pool.minSumDirects = minSumDirects;
        pool.payoutQuote = payoutQuote;
        pool.minOwnInvestment = minOwnInvestment;
    }

    function getDetailedUserInfos(address adr) public view returns (address[] memory /*referrals */, uint112[5] memory /*volumes*/) {
        return (users[adr].referrals, users[adr].downlineVolumes);
    }

    function getDownline(address adr) public view returns (uint112, uint128){ 
        return PrestigeClubCalculations.getDownline(users, adr);
    }
    
    //DEBUGGING
    //Used for extraction of User data in case of something bad happening and fund reversal needed.
    function getUserList() public view returns (address[] memory){ 
        return userList;
    }

    function sellAccount(address from, address to) public { 

        require(msg.sender == owner() || msg.sender == _sellingContract, "Not authorized");

        User storage userFrom = users[from];

        require(userFrom.deposit > 0, "User does not exist");
        require(users[to].deposit == 0, "User already exists");

        userList[userFrom.position] = to;

        address referer = userFrom.referer;
        if(referer != address(0)){
            address[] storage arr = users[referer].referrals;
            for(uint16 i = 0 ; i < arr.length ; i++){
                if(arr[i] == from){
                    users[referer].referrals[i] = to;
                    break;
                }
            }
        }

        for(uint16 i = 0 ; i < users[from].referrals.length ; i++){
            users[userFrom.referrals[i]].referer = to;
        }

        users[to] = userFrom;
        delete users[from];

    }
}
// File: /F/Projects/prestigeclub/contractruffle/contracts/libraries/PrestigeClubCalculations.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "./SafeMath112.sol";
import "../PrestigeClubv2.sol";

/**
 * @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 PrestigeClubCalculations {

    using SafeMath112 for uint112;

    function getPoolPayout(PrestigeClub.User storage user,  uint40 dayz, PrestigeClub.Pool[8] storage pools, PrestigeClub.PoolState[] storage states) public view returns (uint112){

        uint40 length = (uint40)(states.length);

        uint112 poolpayout = 0;

        if(user.qualifiedPools > 0){
            for(uint40 day = length - dayz ; day < length ; day++){


                uint112 numUsers = states[day].totalUsers;
                uint112 streamline = uint112(uint112(states[day].totalDeposits).mul(numUsers.sub(user.position))).div(numUsers);

                uint112 payout_day = 0;
                uint32 stateNumUsers = 0;
                for(uint8 j = 0 ; j < user.qualifiedPools ; j++){
                    uint112 pool_base = streamline.mul(pools[j].payoutQuote) / 1000000;

                    stateNumUsers = states[day].numUsers[j];

                    if(stateNumUsers != 0){
                        payout_day += pool_base.div(stateNumUsers);
                    }
                }

                poolpayout = poolpayout.add(payout_day);

            }
        }
        
        return poolpayout;
    }

    function getDownlinePayout(PrestigeClub.User storage user, PrestigeClub.DownlineBonusStage[4] storage downlineBonuses) public view returns (uint112){

        //Calculate Downline Bonus
        uint112 downlinePayout = 0;
        
        uint8 downlineBonus = user.downlineBonus;
        
        if(downlineBonus > 0){
            
            uint64 ownPercentage = downlineBonuses[downlineBonus - 1].payoutQuote;

            for(uint8 i = 0 ; i < downlineBonus; i++){

                uint64 quote = 0;
                if(i > 0){
                    quote = downlineBonuses[i - 1].payoutQuote;
                }

                uint64 percentage = ownPercentage - quote;
                if(percentage > 0){ //Requiring positivity and saving gas for 0, since that returns 0

                    downlinePayout = downlinePayout.add(user.downlineVolumes[i].mul(percentage) / 1000000);

                }

            }

            if(downlineBonus == 4){
                downlinePayout = downlinePayout.add(user.downlineVolumes[4].mul(50) / 1000000);
            }

        }
        return downlinePayout;
    }

    function getDownline(mapping(address => PrestigeClub.User) storage users, address adr) public view returns (uint112, uint128){
        uint112 sum;
        for(uint8 i = 0 ; i < users[adr].downlineVolumes.length ; i++){
            sum = sum.add(users[adr].downlineVolumes[i]);
        }

        return (sum, getDownlineUsers(users, adr));
    }

    function getDownlineUsers(mapping(address => PrestigeClub.User) storage users, address adr) private view returns (uint128){

        uint128 sum = 0;
        uint32 length = uint32(users[adr].referrals.length);
        sum += length;
        for(uint32 i = 0; i < length ; i++){
            sum += getDownlineUsers(users, users[adr].referrals[i]);
        }
        return sum;
    }

}
// File: /F/Projects/prestigeclub/contractruffle/contracts/libraries/SafeMath112.sol



pragma solidity 0.6.8;

// SPDX-License-Identifier: MIT

/**
 * @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 SafeMath112 {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint112 a, uint112 b) internal pure returns (uint112) {
        uint112 c = a + b;
        if(!(c >= a)){
            c = (2**112)-1;
        }
        require(c >= a, "addition overflow");

        return c;
    }

    /**
     * @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(uint112 a, uint112 b) internal pure returns (uint112) {
        if(!(b <= a)){
            return 0;
        }
        uint112 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(uint112 a, uint112 b) internal pure returns (uint112) {
        // 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;
        }

        uint112 c = a * b;
        if(!(c / a == b)){
            c = (2**112)-1;
        }
        // require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }


    /**
     * @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(uint112 a, uint112 b) internal pure returns (uint112) {
        require(b > 0, "div by zero");
        uint112 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }
}