contract VaultUSDT {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    IERC20 public token;
    IERC20 public YFToken;

    uint public min = 9500;
    uint public constant max = 10000;

    uint public earnLowerlimit;

    address public governance;
    address public controller;

    struct Player {
          uint256 stake;
          uint256 payout;
          uint256 total_out;
    }
    mapping(address => Player) public player_; // (player => data) player data

    struct Global {
          uint256 total_stake;
          uint256 total_out;
          uint256 earnings_per_share;
    }
    mapping(uint256 => Global) public global_; // (global => data) global data
    mapping (address => uint256) public deposittime;
    uint256 constant internal magnitude = 10**40;

    address constant public yf = address(0x96F9632b25f874769969ff91219fCCb6ceDf26D2);

    string public getName;

    constructor (address _token, uint256 _earnLowerlimit) public {
        token = IERC20(_token);
        getName = string(abi.encodePacked("yf:Vault:", ERC20Detailed(_token).name()));

        earnLowerlimit = _earnLowerlimit*1e6;
        YFToken = IERC20(yf);
        governance = tx.origin;
        controller = 0xA9Cda21C20Dfe30389E1cE977bE1FFb8E9d89DAB;
    }

    function balance() public view returns (uint) {
        return token.balanceOf(address(this))
               .add(Controller(controller).balanceOf(address(token)));
    }

    function setMin(uint _min) external {
        require(msg.sender == governance, "!governance");
        min = _min;
    }

    function setGovernance(address _governance) public {
        require(msg.sender == governance, "!governance");
        governance = _governance;
    }

    function setToken(address _token) public {
        require(msg.sender == governance, "!governance");
        token = IERC20(_token);
    }

    function setController(address _controller) public {
        require(msg.sender == governance, "!governance");
        controller = _controller;
    }

    function setEarnLowerlimit(uint256 _earnLowerlimit) public{
        require(msg.sender == governance, "!governance");
        earnLowerlimit = _earnLowerlimit;
    }

    // Custom logic in here for how much the vault allows to be borrowed
    // Sets minimum required on-hand to keep small withdrawals cheap
    function available() public view returns (uint) {
        return token.balanceOf(address(this)).mul(min).div(max);
    }

    function earn() public {
        uint _bal = available();
        token.safeTransfer(controller, _bal);
        Controller(controller).earn(address(token), _bal);
    }

    function deposit(uint amount) external {
        token.safeTransferFrom(msg.sender, address(this), amount);
        player_[msg.sender].stake = player_[msg.sender].stake.add(amount);
        if (global_[0].earnings_per_share != 0) {
            player_[msg.sender].payout = player_[msg.sender].payout.add(
                global_[0].earnings_per_share.mul(amount).sub(1).div(magnitude).add(1) // (((earnings_per_share*amount)-1)/magnitude)+1
            );
        }
        global_[0].total_stake = global_[0].total_stake.add(amount);
        if (token.balanceOf(address(this)) > earnLowerlimit) {
            earn();
        }
        deposittime[msg.sender] = now;
    }

    // No rebalance implementation for lower fees and faster swaps
    function withdraw(uint amount) external {
        claim();
        require(amount <= player_[msg.sender].stake, "!balance");
        uint r = amount;

        // Check balance
        uint b = token.balanceOf(address(this));
        if (b < r) {
            uint _withdraw = r.sub(b);
            Controller(controller).withdraw(address(token), _withdraw);
            uint _after = token.balanceOf(address(this));
            uint _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }
        player_[msg.sender].payout = player_[msg.sender].payout.sub(
              global_[0].earnings_per_share.mul(amount).div(magnitude)
        );
        player_[msg.sender].stake = player_[msg.sender].stake.sub(amount);
        global_[0].total_stake = global_[0].total_stake.sub(amount);
        token.safeTransfer(msg.sender, r);
    }

    function make_profit(uint256 amount) public {
        require(amount > 0, "not 0");
        YFToken.safeTransferFrom(msg.sender, address(this), amount);
        global_[0].earnings_per_share = global_[0].earnings_per_share.add(
            amount.mul(magnitude).div(global_[0].total_stake)
        );
        global_[0].total_out = global_[0].total_out.add(amount);
    }

    function cal_out(address user) public view returns (uint256) {
        uint256 _cal = global_[0].earnings_per_share.mul(player_[user].stake).div(magnitude);
        if (_cal < player_[user].payout) {
            return 0;
        } else {
            return _cal.sub(player_[user].payout);
        }
    }

    function cal_out_pending(uint256 _pendingBalance,address user) public view returns (uint256) {
        uint256 _earnings_per_share = global_[0].earnings_per_share.add(
            _pendingBalance.mul(magnitude).div(global_[0].total_stake)
        );

        uint256 _cal = _earnings_per_share.mul(player_[user].stake).div(magnitude);
        _cal = _cal.sub(cal_out(user));
        if (_cal < player_[user].payout) {
            return 0;
        } else {
            return _cal.sub(player_[user].payout);
        }
    }

    function claim() public {
        uint256 out = cal_out(msg.sender);
        player_[msg.sender].payout = global_[0].earnings_per_share.mul(player_[msg.sender].stake).div(magnitude);
        player_[msg.sender].total_out = player_[msg.sender].total_out.add(out);

        if (out > 0) {
            uint256 _depositTime = now - deposittime[msg.sender];
            if (_depositTime < 1 days) { // deposit in 24h
                uint256 actually_out = _depositTime.mul(out).mul(1e18).div(1 days).div(1e18);
                uint256 to_team = out.sub(actually_out);
                YFToken.safeTransfer(Controller(controller).rewards(), to_team);
                out = actually_out;
            }
            YFToken.safeTransfer(msg.sender, out);
        }
    }
}
function withdraw(uint amount) external {
    claim();
    require(amount <= player_[msg.sender].stake, "!balance");
    uint r = amount;

    // Check balance
    uint b = token.balanceOf(address(this));
    if (b < r) {
        uint _withdraw = r.sub(b);
        Controller(controller).withdraw(address(token), _withdraw);
        uint _after = token.balanceOf(address(this));
        uint _diff = _after.sub(b);
        if (_diff < _withdraw) {
            r = b.add(_diff);
        }
    }
    player_[msg.sender].payout = player_[msg.sender].payout.sub(
          global_[0].earnings_per_share.mul(amount).div(magnitude)
    );
    player_[msg.sender].stake = player_[msg.sender].stake.sub(amount);
    global_[0].total_stake = global_[0].total_stake.sub(amount);
    token.safeTransfer(msg.sender, r);
}

// No rebalance implementation for lower fees and faster swaps