contract CompoundPool is ERC20, ERC20Detailed, Ownable {
    using SafeMath for uint;

    uint256 internal constant PRECISION = 10 ** 18;

    ICompoundERC20 public compoundToken;
    IERC20 public depositToken;
    address public governance;
    address public beneficiary;

    /**
     * @notice Constructor
     * @param _name name of the pool share token
     * @param _symbol symbol of the pool share token
     * @param _comptroller the Compound Comptroller contract used to enter the compoundToken's market
     * @param _compoundToken the Compound Token contract (e.g. cDAI)
     * @param _depositToken the Deposit Token contract (e.g. DAI)
     * @param _beneficiary the address that can withdraw excess deposit tokens (interest/donations)
     */
    constructor(
        string memory _name,
        string memory _symbol,
        IComptroller _comptroller,
        ICompoundERC20 _compoundToken,
        IERC20 _depositToken,
        address _beneficiary
    )
        ERC20Detailed(_name, _symbol, 18)
        public
    {
        compoundToken = _compoundToken;
        depositToken = _depositToken;
        beneficiary = _beneficiary;

        _approveDepositToken(1);

        // Enter compound token market
        address[] memory markets = new address[](1);
        markets[0] = address(compoundToken);
        uint[] memory errors = _comptroller.enterMarkets(markets);
        require(errors[0] == 0, "Failed to enter compound token market");
    }

    /**
     * @dev used to restrict access of functions to the current beneficiary
     */
    modifier onlyBeneficiary() {
        require(msg.sender == beneficiary, "CompoundPool::onlyBeneficiary: Only callable by beneficiary");
        _;
    }

    /**
     * @notice Called by the `owner` to set a new beneficiary
     * @dev This function will fail if called by a non-owner address
     * @param _newBeneficiary The address that will become the new beneficiary
     */
    function updateBeneficiary(address _newBeneficiary) public onlyOwner {
        beneficiary = _newBeneficiary;
    }

    /**
     * @notice The beneficiary calls this function to withdraw excess deposit tokens
     * @dev This function will fail if called by a non-beneficiary or if _amount is higher than the excess deposit tokens
     * @param _to The address that the deposit tokens will be sent to
     * @param _amount The amount of deposit tokens to send to the `_to` address
     */
    function withdrawInterest(address _to, uint256 _amount) public onlyBeneficiary returns (uint256) {
        require(compoundToken.redeemUnderlying(_amount) == 0, "CompoundPool::withdrawInterest: Compound redeem failed");

        //Doing this *after* `redeemUnderlying` so I don't have compoundToken do `exchangeRateCurrent` twice, it's not cheap
        require(depositTokenStoredBalance() >= totalSupply(), "CompoundPool::withdrawInterest: Not enough excess deposit token");

        depositToken.transfer(_to, _amount);
    }

    /**
     * @notice Called by someone wishing to deposit to the bank. This amount, plus previous user's balance, will always be withdrawable
     * @dev Allowance for CompoundPool to transferFrom the msg.sender's balance must be set on the deposit token
     * @param _amount The amount of deposit tokens to deposit
     */
    function deposit(uint256 _amount) public {
        require(depositToken.transferFrom(msg.sender, address(this), _amount), "CompoundPool::deposit: Transfer failed");

        _approveDepositToken(_amount);

        require(compoundToken.mint(_amount) == 0, "CompoundPool::deposit: Compound mint failed");
    
        _mint(msg.sender, _amount);
    }

    /**
     * @notice Called by someone wishing to withdraw from the bank
     * @dev This will fail if msg.sender doesn't have at least _amount pool share tokens
     * @param _amount The amount of deposit tokens to withdraw
     */
    function withdraw(uint256 _amount) public {
        _burn(msg.sender, _amount);

        require(compoundToken.redeemUnderlying(_amount) == 0, "CompoundPool::withdraw: Compound redeem failed");

        require(depositToken.transfer(msg.sender, _amount), "CompoundPool::withdraw: Transfer failed");
    }

    /**
     * @notice Called by someone wishing to donate to the bank. This amount will *not* be added to users balance, and will be usable by the beneficiary.
     * @dev Allowance for CompoundPool to transferFrom the msg.sender's balance must be set on the deposit token
     * @param _amount The amount of deposit tokens to donate
     */
    function donate(uint256 _amount) public {
        require(depositToken.transferFrom(msg.sender, address(this), _amount), "CompoundPool::donate: Transfer failed");

        _approveDepositToken(_amount);

        require(compoundToken.mint(_amount) == 0, "CompoundPool::donate: Compound mint failed");
    }

    /**
     * @notice Returns the amount of deposit tokens that are usable by the beneficiary. Basically, interestEarned+donations
     * @dev Allowance for CompoundPool to transferFrom the msg.sender's balance must be set on the deposit token
     */
    function excessDepositTokens() public returns (uint256) {
        return compoundToken.exchangeRateCurrent().mul(compoundToken.balanceOf(address(this))).div(PRECISION).sub(totalSupply());
    }

    function depositTokenStoredBalance() internal returns (uint256) {
        return compoundToken.exchangeRateStored().mul(compoundToken.balanceOf(address(this))).div(PRECISION);
    }

    function _approveDepositToken(uint256 _minimum) internal {
        if(depositToken.allowance(address(this), address(compoundToken)) < _minimum){
            depositToken.approve(address(compoundToken),uint256(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));
        }
    }
}
/**
 * @title CompoundPool
 * @author Nate Welch <github.com/flyging>
 * @notice Based on Zefram Lou's implementation https://github.com/ZeframLou/pooled-cdai
 * @dev A bank that will pool compound tokens and allows the beneficiary to withdraw
 */
function updateBeneficiary(address _newBeneficiary) public onlyOwner {
    beneficiary = _newBeneficiary;
}

/**
 * @notice Called by the `owner` to set a new beneficiary
 * @dev This function will fail if called by a non-owner address
 * @param _newBeneficiary The address that will become the new beneficiary
 */