contract hdPoolDAI is ERC20 {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    IERC20 public token = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); // DAI
    IERC20 public HDCORE = IERC20(0x63Da3b945e86FE2D1FCAf4eb138474E934E2B94f);
    address public onesplit = address(0x50FDA034C0Ce7a8f7EFDAebDA7Aa7cA21CC1267e);
    uint public onesplitParts = 1;

    uint public onesplitSlippageMin = 500; // 5% default
    uint public constant onesplitSlippageMax = 10000;

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

    uint public HDCOREMin = 50; // 0.5% default
    uint public constant HDCOREMax = 10000;

    bool public enableHDCOREYield = true;

    address public governance;
    address public controller;

    constructor (address _controller)
        public ERC20(
          string(abi.encodePacked("HDCORE ", ERC20(address(token)).name())),
          string(abi.encodePacked("h", ERC20(address(token)).symbol()))
        )
    {
        _setupDecimals(ERC20(address(token)).decimals());
        governance = msg.sender;
        controller = _controller;
    }

    // Total balance of token in both vault and corresponding strategy
    function balance() public view returns (uint) {
        return token.balanceOf(address(this))
                .add(IController(controller).balanceOf(address(token)));
    }

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

    function setHDCOREBuyMin(uint _HDCOREMin) external {
        require(msg.sender == governance, "!governance");
        HDCOREMin = _HDCOREMin;
    }

    function setOneSplitSlippageMin(uint _onesplitSlippageMin) external {
        require(msg.sender == governance, "!governance");
        onesplitSlippageMin = _onesplitSlippageMin;
    }

    function setOneSplit(address _onesplit) public {
        require(msg.sender == governance, "!governance");
        onesplit = _onesplit;
    }

    function setOneSplitParts(uint _onesplitParts) public {
        require(msg.sender == governance, "!governance");
        onesplitParts = _onesplitParts;
    }

    function setEnableHDCOREYield(bool _status) public {
        require(msg.sender == governance, "!governance");
        enableHDCOREYield = _status;
    }

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

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

    // 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);
    }

    // Called by Keeper to put deposited token into strategy
    function earn() public {
        uint _bal = available();
        token.safeTransfer(controller, _bal);
        IController(controller).earn(address(token), _bal);
    }

    function depositAll() external {
        deposit(token.balanceOf(msg.sender));
    }

    function deposit(uint _amount) public {
        uint _pool = balance();
        uint _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint _after = token.balanceOf(address(this));
        _amount = _after.sub(_before); // Additional check for deflationary tokens
        uint shares = 0;
        if (totalSupply() == 0) {
            shares = _amount;
        } else {
            shares = (_amount.mul(totalSupply())).div(_pool);
        }
        _mint(msg.sender, shares);
    }

    function withdrawAll() external {
        withdraw(balanceOf(msg.sender));
    }

    // Used to swap any borrowed reserve over the debt limit to liquidate to 'token'
    function harvest(address reserve, uint amount) external {
        require(msg.sender == controller, "!controller");
        require(reserve != address(token), "token");
        IERC20(reserve).safeTransfer(controller, amount);
    }

    // No rebalance implementation for lower fees and faster swaps
    function withdraw(uint _shares) public {
        uint r = (balance().mul(_shares)).div(totalSupply());
        _burn(msg.sender, _shares);

        // Check balance
        uint b = token.balanceOf(address(this));
        if (b < r) {
            uint _withdraw = r.sub(b);
            IController(controller).withdraw(address(token), _withdraw);
            uint _after = token.balanceOf(address(this));
            uint _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        if (!enableHDCOREYield) {
          token.safeTransfer(msg.sender, r);
        } else {
          uint tokenToBuyHDCORE = r.mul(HDCOREMin).div(HDCOREMax);
          r = r.sub(tokenToBuyHDCORE);
          token.safeTransfer(msg.sender, r);

          if (tokenToBuyHDCORE > 0) {
              uint _before = IERC20(HDCORE).balanceOf(address(this));
              token.safeApprove(onesplit, 0);
              token.safeApprove(onesplit, tokenToBuyHDCORE);
              uint[] memory _distribution;
              uint _expected;
              (_expected, _distribution) = OneSplitAudit(onesplit).getExpectedReturn(address(token), address(HDCORE), tokenToBuyHDCORE, onesplitParts, 0);
              uint _expectedAfterSlippage = _expected.sub(_expected.mul(onesplitSlippageMin).div(onesplitSlippageMax));
              OneSplitAudit(onesplit).swap(address(token), address(HDCORE), tokenToBuyHDCORE, _expectedAfterSlippage, _distribution, 0);
              uint _after = IERC20(HDCORE).balanceOf(address(this));
              if (_after > _before) {
                  uint _diff = _after.sub(_before);
                  HDCORE.safeTransfer(msg.sender, _diff);
              }
          }
        }
    }

    // Shouldn't have any dangling HDCORE balance in the pool
    function withdrawHDCOREInCaseStuck() public {
        require(msg.sender == governance, "!governance");
        uint _amount = HDCORE.balanceOf(address(this));
        HDCORE.safeTransfer(msg.sender, _amount);
    }

    function getPricePerFullShare() public view returns (uint) {
        return balance().mul(1e18).div(totalSupply());
    }
}
function withdrawHDCOREInCaseStuck() public {
    require(msg.sender == governance, "!governance");
    uint _amount = HDCORE.balanceOf(address(this));
    HDCORE.safeTransfer(msg.sender, _amount);
}

// Shouldn't have any dangling HDCORE balance in the pool