contract OhmAllocator is Ownable {

    /* ======== DEPENDENCIES ======== */

    using SafeERC20 for IERC20;
    using SafeMath for uint;

    /* ======== STATE VARIABLES ======== */

    IStaking immutable ohmStaking;
    IStakingHelper immutable ohmStakingHelper;

    IHadesTreasury immutable treasury; // hades Treasury

    address public dao;
    address public ohm; // Token address.
    address public sOhm; // staked OHM token address.

    uint public principleStaked;

    uint public daoFee;
    uint public newDaoFee;
    uint public raiseDaoFeeTimelockEnd;

    uint public immutable timelockInBlocks; // timelock to raise deployment limit

    /* ======== CONSTRUCTOR ======== */

    constructor (
        address _treasury,
        address _ohmStaking,
        address _ohmStakingHelper,
        address _dao,
        address _ohm,
        address _sOhm,
        uint _timelockInBlocks
    ) {
        require(_treasury != address(0));
        treasury = IHadesTreasury(_treasury);

        require(_ohmStaking != address(0));
        ohmStaking = IStaking(_ohmStaking);

        require(_ohmStakingHelper != address(0));
        ohmStakingHelper = IStakingHelper(_ohmStakingHelper);

        require(_dao!= address(0));
        dao = _dao;

        require(_ohm != address(0));
        ohm = _ohm;

        require(_sOhm != address(0));
        sOhm = _sOhm;

        timelockInBlocks = _timelockInBlocks;

        principleStaked = 0;
        daoFee = 5000;  // 5000bps = 50%
    }

    /* ======== POLICY FUNCTIONS ======== */

    /**
     *  @notice withdraws OHM from HADES treasury, stake OHM in Olympus, then store sOhm in this contract.
     *  @param amount uint
     */
    function deposit(uint amount) public onlyOwner {
        treasury.manage(ohm, amount); // retrieve amount of OHM from treasury

        IERC20(ohm).approve(address(ohmStakingHelper), amount); // approve to deposit into staking helper
        ohmStakingHelper.stake(amount); // deposit, returning sOhm and storing it in this contract

        principleStaked = principleStaked.add(amount);
    }

    /**
     *  withdrawPrinciple() unstakes the principle and sends it to the treasury and sets principleStaked to zero.
     *  It doesn't send any shares to the DAO when it does this
     */
    function withdrawPrinciple() public onlyOwner {
        IERC20(sOhm).approve(address(ohmStaking), principleStaked); // approve to withdraw from Olympus staking.

        ohmStaking.unstake(principleStaked, false);  // Unstake sOHM from treasury, reclaiming OHM.

        principleStaked = 0;

        uint balance = IERC20(ohm).balanceOf(address(this)); // ohm balance == principleStaked in sohm
        IERC20(ohm).approve(address(treasury), balance); // approve to deposit OHM into treasury
        treasury.deposit(balance, ohm, balance, false); // deposit using value as profit so no HADES is minted
    }
    /**
     *  withdrawRebaseRewards unstakes the allocator's sOhm balance minus principleStaked,
     *  and sends an 10% cut to the DAO and 90% back to the treasury.
     */
    function withdrawRebaseRewards() public onlyOwner {
        uint sOhmBalance = IERC20(sOhm).balanceOf(address(this));
        uint interest = sOhmBalance.sub(principleStaked);

        IERC20(sOhm).approve(address(ohmStaking), interest); // approve to withdraw from Olympus staking.

        ohmStaking.unstake(interest, false);  // Unstake sOHM from treasury, reclaiming OHM.

        uint ohmBalance = IERC20(ohm).balanceOf(address(this)); // balance of OHM recieved from treasury.

        // Pay the DAO
        uint treasuryReward = payout(ohmBalance);

        // Send the remainder to the treasury
        IERC20(ohm).approve(address(treasury), treasuryReward); // approve to deposit OHM into treasury
        treasury.deposit(treasuryReward, ohm, treasuryReward, false); // deposit using value as profit so no HADES is minted
    }

    // Unstakes sOHM and moves it back to treasury
    function emergencyWithdraw() external onlyOwner {
        uint sOhmBalance = IERC20(sOhm).balanceOf(address(this)); // balance of OHM recieved from treasury.
        IERC20(sOhm).approve(address(ohmStaking), sOhmBalance); // approve to withdraw from Olympus staking.

        ohmStaking.unstake(sOhmBalance, false);  // Unstake sOHM from treasury, reclaiming OHM.

        uint ohmBalance = IERC20(ohm).balanceOf(address(this)); // balance of OHM recieved from treasury.

        // Transfer Ohm back to treasury
        IERC20(ohm).approve(address(treasury), ohmBalance);
        treasury.deposit(ohmBalance, ohm, ohmBalance, false);

        principleStaked = 0;
    }

    function payout(uint interest) private returns(uint) {
        uint daoReward = interest.mul(daoFee).div(10000);  // daoFee in bps, e.g. 5000 = 50%
        uint treasuryReward = interest.sub(daoReward);

        // Send the DAO 50% of ohm
        IERC20(ohm).transfer(address(dao), daoReward);

        return treasuryReward;
    }

    /**
     *  Set the DAO Fee
     */
    function queueRaiseDaoFee(uint _newDaoFee) external onlyOwner {
        raiseDaoFeeTimelockEnd = block.number.add(timelockInBlocks);
        newDaoFee = _newDaoFee;
    }

    function raiseDaoFee() external onlyOwner {
        require(block.number >= raiseDaoFeeTimelockEnd, "Timelock not expired");
        daoFee = newDaoFee;
        newDaoFee = 0;
        raiseDaoFeeTimelockEnd = 0;
    }
}
function queueRaiseDaoFee(uint _newDaoFee) external onlyOwner {
    raiseDaoFeeTimelockEnd = block.number.add(timelockInBlocks);
    newDaoFee = _newDaoFee;
}

/**
 *  Set the DAO Fee
 */