thinkthink

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.6.12;

import "./BEP20.sol";

interface IUniswapV2Factory { event PairCreated(address indexed token0, address indexed token1, address pair, uint);

function feeTo() external view returns (address);
function feeToSetter() external view returns (address);

function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);

function createPair(address tokenA, address tokenB) external returns (address pair);

function setFeeTo(address) external;
function setFeeToSetter(address) external;

}

interface IUniswapV2Pair { event Approval(address indexed owner, address indexed spender, uint value); event Transfer(address indexed from, address indexed to, uint value);

function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);

function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);

function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;

event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
    address indexed sender,
    uint amount0In,
    uint amount1In,
    uint amount0Out,
    uint amount1Out,
    address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);

function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);

function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;

function initialize(address, address) external;

}

interface IUniswapV2Router01 { function factory() external pure returns (address); function WETH() external pure returns (address);

function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
    address token,
    uint amountTokenDesired,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
    address token,
    uint liquidity,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline,
    bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
    uint amountOut,
    uint amountInMax,
    address[] calldata path,
    address to,
    uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);

function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);

}

interface IUniswapV2Router02 is IUniswapV2Router01 { function removeLiquidityETHSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external returns (uint amountETH); function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s ) external returns (uint amountETH);

function swapExactTokensForTokensSupportingFeeOnTransferTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external;

}

abstract contract Ownable is Context { address private _owner;

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

/**
 * @dev Initializes the contract setting the deployer as the initial owner.
 */
constructor() internal {
    address msgSender = _msgSender();
    _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 == _msgSender(), "Ownable: caller is not the owner");
    _;
}

/**
 * @dev Leaves the contract without owner. It will not be possible to call
 * `onlyOwner` functions anymore. Can only be called by the current owner.
 *
 * NOTE: Renouncing ownership will leave the contract without an owner,
 * thereby removing any functionality that is only available to the owner.
 */
function renounceOwnership() public virtual onlyOwner {
    emit OwnershipTransferred(_owner, address(0));
    _owner = address(0);
}

/**
 * @dev Transfers ownership of the contract to a new account (`newOwner`).
 * Can only be called by the current owner.
 */
function transferOwnership(address newOwner) public virtual onlyOwner {
    require(
        newOwner != address(0),
        "Ownable: new owner is the zero address"
    );
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
}

}

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false library TransferHelper { function safeApprove( address token, address to, uint256 value ) internal { // bytes4(keccak256(bytes('approve(address,uint256)'))); (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); require( success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper::safeApprove: approve failed' ); }

function safeTransfer(
    address token,
    address to,
    uint256 value
) internal {
    // bytes4(keccak256(bytes('transfer(address,uint256)')));
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
    require(
        success && (data.length == 0 || abi.decode(data, (bool))),
        'TransferHelper::safeTransfer: transfer failed'
    );
}

function safeTransferFrom(
    address token,
    address from,
    address to,
    uint256 value
) internal {
    // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
    require(
        success && (data.length == 0 || abi.decode(data, (bool))),
        'TransferHelper::transferFrom: transferFrom failed'
    );
}

function safeTransferETH(address to, uint256 value) internal {
    (bool success, ) = to.call{value: value}(new bytes(0));
    require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}

}

contract Stake is Ownable{ using SafeMath for uint256;

struct PoolInfo {
    address token0;
    address token1;
    address awardToken;
    uint awardPerDay;
    uint powerSum;
    uint debtSum;
    uint accPerPower;
    uint minLimit;
    uint lastRewardTime;
}

mapping(uint8 => PoolInfo) public poolInfo;

//bsc
address public usdt = address(0);
//rinkeby
// address public usdt = address(0x10681E51eA76b0F66fe6a9433a4326Fd2B5c84c5);
//bsctest
// address public usdt = address(0x7860554d6A0c9094299c5f9A41fFB305e2283f43);

address public destroyAddress = address(0x000000000000000000000000000000000000dEaD);

address public coes = address(0);

address public coe = address(0);

uint256 public coe_end = block.timestamp + 7 days;

mapping(address => bool) public isCoe;

address public usdtReceiver = address(0);

bool public init = false;

uint256[] public referRewardRate = [25,5,5,5,5,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2];
uint256[] public upRewardRate = [2, 2, 2, 2, 2];

struct UserInfo {
    uint power;
    uint debt;
    uint static_award;
    uint static_usdt;
    uint award;
    uint award_in_usdt;
}

struct NodeInfo {
    bool active;
    uint count;
    address referrer;
    mapping(uint => address) nodes;
}

mapping(address => NodeInfo) public nodeInfo;
mapping(uint8 => mapping(address => UserInfo)) public userInfo;

IUniswapV2Router02 public uniswapV2Router;
IUniswapV2Factory public factory;

address[] public users;
mapping(address => uint256) public userIndexes;
mapping(address => bool) public isUser;

event Acitve(address indexed user, address upline);
event Award(uint8 pid, address indexed user, address upline, uint amount, uint award, uint g);
event Add(uint8 pid, address indexed user, uint amount, uint amount2, uint multiple);
event Harvest(uint8 pid, address indexed user, uint amount, uint award);

function getChild(address addr, uint index) public view returns (address) {
    return nodeInfo[addr].nodes[index];
}

function getPowerSum(uint8 pid) public view returns (uint) {
    PoolInfo storage pool = poolInfo[pid];
    if(pool.powerSum > pool.debtSum.div(3)) {
        return pool.powerSum.sub(pool.debtSum.div(3));
    }else{
        return 0;
    }
}
/*
 * 输出多少币
 */
function pendingAward(uint8 pid, address addr) public view returns (uint) {
    PoolInfo storage pool = poolInfo[pid];
    UserInfo storage user = userInfo[pid][addr];

    uint accPerPower_tmp = pool.accPerPower;
    if(now > pool.lastRewardTime && pool.powerSum > 0) {
        uint multiplier = now.sub(pool.lastRewardTime);

        accPerPower_tmp = accPerPower_tmp.add(pool.awardPerDay.mul(multiplier).mul(1e18).div(1 days).div(pool.powerSum));
    }
    // award in token
    uint award = user.power.mul(accPerPower_tmp).div(1e18).sub(user.debt);
    uint power_left = getUserPowerLeft(pid, addr);
    if(power_left == 0) {
        return 0;
    }
    address[] memory path = new address[](2);
    path[0] = address(pool.awardToken);
    path[1] = address(usdt);
    //币转token数量
    uint[] memory amounts_in = uniswapV2Router.getAmountsIn(1e18, path);
    if(award > amounts_in[0].mul(power_left).div(1e18)) {
        award = amounts_in[0].mul(power_left).div(1e18);
    }
    return award;
}

function getUserPowerLeft(uint8 pid, address addr) public view returns (uint) {
    UserInfo storage user = userInfo[pid][addr];
    if(user.power > user.static_usdt.add(user.award_in_usdt)) {
        return user.power.sub(user.static_usdt.add(user.award_in_usdt));
    }else{
        return 0;
    }
}

function updatePool(uint8 pid) public {
    PoolInfo storage pool = poolInfo[pid];

    if(pool.powerSum == 0) {
        pool.lastRewardTime = now;
        return;
    }
    uint multiplier = now.sub(pool.lastRewardTime);
    pool.accPerPower = pool.accPerPower.add(pool.awardPerDay.mul(multiplier).mul(1e18).div(1 days).div(pool.powerSum));
    pool.lastRewardTime = now;
}

function doActive(address addr) public returns (bool) {
    NodeInfo storage upnode = nodeInfo[addr];
    require(upnode.active, 'upnode must be actived');
    NodeInfo storage node = nodeInfo[msg.sender];
    require(node.active == false, 'have active');
    node.active = true;
    node.referrer = addr;

    upnode.nodes[upnode.count] = msg.sender;
    upnode.count += 1;

    emit Acitve(msg.sender, addr);
    return true;
}

function harvest(uint8 pid) public returns (bool) {
    _harvest(pid, msg.sender);
}

function _harvest(uint8 pid, address addr) private returns (bool) {
    PoolInfo storage pool = poolInfo[pid];
    UserInfo storage user = userInfo[pid][addr];
    uint power_left = getUserPowerLeft(pid, addr);
    updatePool(pid);
    if(power_left == 0) {
        return true;
    }
    //award in token
    uint pending = pendingAward(pid, addr);
    address[] memory path = new address[](2);
    path[0] = address(pool.awardToken);
    path[1] = address(usdt);
    if(pending > 0) {
        uint[] memory amounts_out = uniswapV2Router.getAmountsOut(pending, path);
        //award in awardToken
        if(amounts_out[1]>0) {
            TransferHelper.safeTransfer(pool.awardToken, addr, pending);
            user.debt = user.power.mul(pool.accPerPower).div(1e18);
            user.static_award += pending;
            user.static_usdt += amounts_out[1];
            pool.debtSum += amounts_out[1];
        
            emit Harvest(pid, addr, amounts_out[1], pending);
        }
        user.debt = user.power.mul(pool.accPerPower).div(1e18);
    }

    return true;
}

function addToken2(uint8 pid) public returns (bool) {
    require(!isCoe[msg.sender], 'only coe once');
    require(block.timestamp <= coe_end, 'coe end');
    
    updatePool(pid);
    
    PoolInfo storage pool = poolInfo[pid];
    UserInfo storage user = userInfo[pid][msg.sender];

    uint amount = 400 * 1e18;
    uint power = 2000 * 1e18;
    require(BEP20(coe).balanceOf(msg.sender) >= amount, 'coe balance insufficient');
    require(BEP20(coe).allowance(msg.sender, address(this)) >= amount, 'coe allow insufficient');
    //收币
    TransferHelper.safeTransferFrom(coe, msg.sender, destroyAddress, amount);

    user.power = user.power.add(power);
    user.debt = user.power.mul(pool.accPerPower).div(1e18);
    pool.powerSum = pool.powerSum.add(power);

    if(!isUser[msg.sender]) {
        userIndexes[msg.sender] = users.length;
        users.push(msg.sender);
        isUser[msg.sender] = true;
    }

    isCoe[msg.sender] = true;
}

function addToken(uint8 pid, uint amount) public returns (uint256) {
    PoolInfo storage pool = poolInfo[pid];
    UserInfo storage user = userInfo[pid][msg.sender];

    updatePool(pid);
    uint power_left = getUserPowerLeft(pid, msg.sender);
    if(power_left > 0) {
        uint pending = pendingAward(pid, msg.sender);
        address[] memory path = new address[](2);
        path[0] = address(pool.awardToken);
        path[1] = address(usdt);
        uint[] memory amounts_out = uniswapV2Router.getAmountsOut(pending, path);
        if( pending > 0 ) {
            TransferHelper.safeTransfer(pool.awardToken, msg.sender, pending);
            user.static_award += pending;
            user.static_usdt += amounts_out[1];
            pool.debtSum += amounts_out[1];
        }
    }
    uint multiple = 0;
    if(amount > 0) {
        require(amount >= pool.minLimit, 'less then min');
        address[] memory path = new address[](2);
        multiple = getMultiple(user.power + pool.powerSum);
        if(pool.token0 != address(0)) {
            require(BEP20(pool.token0).balanceOf(msg.sender) >= amount, 'token0 balance insufficient');
            require(BEP20(pool.token0).allowance(msg.sender, address(this)) >= amount, 'token0 allow insufficient');
            //收币
            TransferHelper.safeTransferFrom(pool.token0, msg.sender, address(this), amount);
            user.power = user.power.add(amount.mul(multiple));
            pool.powerSum = pool.powerSum.add(amount.mul(multiple));

            if(!isUser[msg.sender]) {
                userIndexes[msg.sender] = users.length;
                users.push(msg.sender);
                isUser[msg.sender] = true;
            }
        }
        uint amount2 = 0;
        if(pool.token1 != address(0)) {
            path[0] = address(pool.token1);
            path[1] = address(usdt);
            uint[] memory amounts_in = uniswapV2Router.getAmountsIn(amount, path);
            amount2 = amounts_in[0];
            require(BEP20(pool.token1).balanceOf(msg.sender) >= amount2, 'token1 balance insufficient');
            require(BEP20(pool.token1).allowance(msg.sender, address(this)) >= amount2, 'token1 allow insufficient');
            TransferHelper.safeTransferFrom(pool.token1, msg.sender, address(destroyAddress), amount2);
            user.power = user.power.add(amount.mul(multiple));
            pool.powerSum = pool.powerSum.add(amount.mul(multiple));
        }
        emit Add(pid, msg.sender, amount, amount2, multiple);

        //奖励
        doAward(pid, msg.sender, amount);
    }

    user.debt = user.power.mul(pool.accPerPower).div(1e18);
    return multiple;
}

function doAward(uint8 pid, address from, uint256 amount) internal {
    PoolInfo storage pool = poolInfo[pid];

    address addr = from;
    
    for(uint256 g=0; g<25; g++) {
        uint256 reward = amount.mul(referRewardRate[g]).div(100);
        NodeInfo storage userNode = nodeInfo[addr];
        UserInfo storage upline = userInfo[pid][userNode.referrer];
        NodeInfo storage upNode = nodeInfo[userNode.referrer];
        uint power_left = getUserPowerLeft(pid, userNode.referrer);

        address[] memory path = new address[](2);
        path[0] = address(pool.awardToken);
        path[1] = address(usdt);
        uint[] memory amounts_in = uniswapV2Router.getAmountsIn(reward, path);            

        if(userNode.referrer != address(0) && upNode.count > g && power_left >= reward) {

            pool.debtSum += reward;
            upline.award += amounts_in[0];
            upline.award_in_usdt += reward;

            TransferHelper.safeTransfer(pool.awardToken, userNode.referrer, amounts_in[0]);
            emit Award(pid, from, userNode.referrer, reward, amounts_in[0], g);
        }else{
            //销毁
            TransferHelper.safeTransfer(pool.awardToken, destroyAddress, amounts_in[0]);
        }
        addr = userNode.referrer;
    }

    uint256 userIndex = userIndexes[from];
    for(uint256 k=0; k<5; k++) {
        if(userIndex + k + 1 < users.length) {
            address downline = users[userIndex + k + 1];
            uint256 reward = amount.mul(upRewardRate[k]).div(100);
            uint power_left = getUserPowerLeft(pid, downline);
            NodeInfo storage downNode = nodeInfo[downline];
            UserInfo storage downUser = userInfo[pid][downline];

            address[] memory path = new address[](2);
            path[0] = address(pool.awardToken);
            path[1] = address(usdt);
            uint[] memory amounts_in = uniswapV2Router.getAmountsIn(reward, path);
            
            if(downline != address(0) && downNode.count > k && power_left >= reward) {

                pool.debtSum += reward;
                downUser.award += amounts_in[0];
                downUser.award_in_usdt += reward;

                TransferHelper.safeTransfer(pool.awardToken, downline, amounts_in[0]);
                emit Award(pid, from, downline, reward, amounts_in[0], k);
            }else{
                //销毁
                TransferHelper.safeTransfer(pool.awardToken, destroyAddress, amounts_in[0]);
            }
        }else{
            break;
        }
    }
}

function getMultiple(uint256 seed) internal view returns (uint256) {
    uint256 rand = getRandom(seed, 100);
    if(rand < 80) {
        return 2;
    } else if (rand < 90) {
        return 3;
    } else if (rand < 97) {
        return 4;
    } else if (rand < 98) {
        return 5;
    } else if (rand < 99) {
        return 6;
    } else {
        return 7;
    }
}

function getRandom(uint seed, uint max) internal view returns(uint256) {
    uint256 _v = psuedoRandomness(seed) % max;
    return _v;
}

function psuedoRandomness(uint seed) internal view returns(uint256) {
    return uint256(keccak256(abi.encodePacked(
        block.timestamp + block.difficulty +
        ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)) +
        block.gaslimit + 
        ((uint256(keccak256(abi.encodePacked(_msgSender())))) / (now)) +
        block.number + seed
    )));
}

}