/**
 Based on GovernorALPHA from Compound.
 -No delegates
*/

pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;

library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, 'SafeMath: addition overflow');

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, 'SafeMath: subtraction overflow');
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, 'SafeMath: multiplication overflow');

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, 'SafeMath: division by zero');
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, 'SafeMath: modulo by zero');
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

// COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol
// Copyright 2020 Compound Labs, Inc.
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
contract GovernorAlpha {
    /// @notice The name of this contract
    string public constant name = 'TLTF Governor';

    /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed
    function quorumVotes() public view returns (uint256) {
        return
            SafeMath.div(SafeMath.mul(pheezez.votesAvailable(), qRatio), 100);
    } // Ratio can be governed

    /// @notice The number of votes required in order for a voter to become a proposer
    function proposalThreshold() public view returns (uint256) {
        return
            SafeMath.div(SafeMath.mul(pheezez.votesAvailable(), tRatio), 100);
    } // Ratio can be governed

    /// @notice The maximum number of actions that can be included in a proposal
    function proposalMaxOperations() public pure returns (uint256) {
        return 10;
    } // 10 actions

    /// @notice The delay before voting on a proposal may take place, once proposed
    function votingDelay() public pure returns (uint256) {
        return 1;
    } // 1 block

    /// @notice The duration of voting on a proposal, in blocks
    function votingPeriod() public view returns (uint256) {
        return vPeriod;
    } // ~3 days in blocks (assuming 13s blocks) ***CHANGE WHEN NEEDED*****

    /// @notice The address of the Timelock
    TimelockInterface public timelock;

    /// @notice The address of the PHEEZEZ token
    PHEEZEZInterface public pheezez;

    /// @notice The address of the Treasury, for paying FRT on each transaction.
    FRTTreasury public treasury;

    FRTPoolPHZT public poolPHZT;

    /// @notice The address of the Governor Guardian. Initialy, the dev, until tests are completed.
    address public guardian;

    /// @notice The total number of proposals
    uint256 public proposalCount;

    /// @notice Delay for allowing proposals to be made. Relative to the halving rate of the Token.
    uint256 public proposeDelay = 11058124;

    /// @notice Allows quorum Ratio to be changed.
    uint256 public qRatio = 4;

    /// @notice Allows threshold ratio to be changed.
    uint256 public tRatio = 1;

    /// @notice Allows vote ratio to be changed.
    uint256 public vRatio = 2;

    /// @notice Allows votation Period to be changed.
    uint256 public vPeriod = 19938;

    /// @notice Allows queueReward Ratio to be changed.
    uint256 public queueReward = 1;

    /// @notice Allows voteReward ratio to be changed.
    uint256 public voteReward = 1;

    /// @notice Allows execReward ratio to be changed.
    uint256 public execReward = 2;

    /// @notice Allows proposeReward ratio to be changed.
    uint256 public proposeReward = 4;

    struct Proposal {
        //Unique id for looking up a proposal
        uint256 id;
        //Creator of the proposal
        address proposer;
        //The timestamp that the proposal will be available for execution, set once the vote succeeds
        uint256 eta;
        //the ordered list of target addresses for calls to be made
        address[] targets;
        //The ordered list of values (i.e. msg.value) to be passed to the calls to be made
        uint256[] values;
        //The ordered list of function signatures to be called
        string[] signatures;
        //The ordered list of calldata to be passed to each call
        bytes[] calldatas;
        //The block at which voting begins: holders must delegate their votes prior to this block
        uint256 startBlock;
        //The block at which voting ends: votes must be cast prior to this block
        uint256 endBlock;
        //Current number of votes in favor of this proposal
        uint256 forVotes;
        //Current number of votes in opposition to this proposal
        uint256 againstVotes;
        //Flag marking whether the proposal has been canceled
        bool canceled;
        //Flag marking whether the proposal has been executed
        bool executed;
        //Flag for marking that a proposal succeeded so it does not get Defeated when quorum% updates.
        uint256 quorumatPropose;
        //Receipts of ballots for the entire set of voters
        mapping(address => Receipt) receipts;
    }

    //Ballot receipt record for a voter
    struct Receipt {
        //Whether or not a vote has been cast
        bool hasVoted;
        //Whether or not the voter supports the proposal
        bool support;
        //The number of votes the voter had, which were cast
        uint256 votes;
    }

    /// @notice Possible states that a proposal may be in
    enum ProposalState {
        Pending,
        Active,
        Canceled,
        Defeated,
        Succeeded,
        Queued,
        Expired,
        Executed
    }

    /// @notice The official record of all proposals ever proposed
    mapping(uint256 => Proposal) public proposals;

    /// @notice The latest proposal for each proposer
    mapping(address => uint256) public latestProposalIds;

    /// @notice The EIP-712 typehash for the contract's domain
    bytes32 public constant DOMAIN_TYPEHASH = keccak256(
        'EIP712Domain(string name,uint256 chainId,address verifyingContract)'
    );

    /// @notice The EIP-712 typehash for the ballot struct used by the contract
    bytes32 public constant BALLOT_TYPEHASH = keccak256(
        'Ballot(uint256 proposalId,bool support)'
    );

    /// @notice An event emitted when a new proposal is created
    event ProposalCreated(
        uint256 id,
        address proposer,
        address[] targets,
        uint256[] values,
        string[] signatures,
        bytes[] calldatas,
        uint256 startBlock,
        uint256 endBlock,
        string description
    );

    /// @notice An event emitted when a vote has been cast on a proposal
    event VoteCast(
        address voter,
        uint256 proposalId,
        bool support,
        uint256 votes
    );

    /// @notice An event emitted when a proposal has been canceled
    event ProposalCanceled(uint256 id);

    /// @notice An event emitted when a proposal has been queued in the Timelock
    event ProposalQueued(uint256 id, uint256 eta);

    /// @notice An event emitted when a proposal has been executed in the Timelock
    event ProposalExecuted(uint256 id);

    constructor(
        address timelock_,
        address pheezez_,
        address treasury_,
        address poolPHZT_
    ) public {
        timelock = TimelockInterface(timelock_);
        pheezez = PHEEZEZInterface(pheezez_);
        treasury = FRTTreasury(treasury_);
        poolPHZT = FRTPoolPHZT(poolPHZT_);
        guardian = msg.sender;
    }

    function propose(
        address[] memory targets,
        uint256[] memory values,
        string[] memory signatures,
        bytes[] memory calldatas,
        string memory description
    ) public returns (uint256) {
        require(
            block.number > proposeDelay,
            'GovernorAlpha::propose: Not allowed to propose yet'
        );
        require(
            (add256(pheezez.getPriorVotes(msg.sender, sub256(block.number, 1)),
             poolPHZT.getPriorVotesinPool(msg.sender, sub256(block.number, 1))))  >
                proposalThreshold(),
            'GovernorAlpha::propose: proposer votes below proposal threshold'
        );
        require(
            targets.length == values.length &&
                targets.length == signatures.length &&
                targets.length == calldatas.length,
            'GovernorAlpha::propose: proposal function information mismatch'
        );
        require(
            targets.length != 0,
            'GovernorAlpha::propose: must provide actions'
        );
        require(
            targets.length <= proposalMaxOperations(),
            'GovernorAlpha::propose: too many actions'
        );

        uint256 latestProposalId = latestProposalIds[msg.sender];
        if (latestProposalId != 0) {
            ProposalState proposersLatestProposalState = state(
                latestProposalId
            );
            require(
                proposersLatestProposalState != ProposalState.Active,
                'GovernorAlpha::propose: one live proposal per proposer, found an already active proposal'
            );
            require(
                proposersLatestProposalState != ProposalState.Pending,
                'GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal'
            );
        }

        uint256 startBlock = add256(block.number, votingDelay());
        uint256 endBlock = add256(startBlock, votingPeriod());
        uint256 quorum = quorumVotes();

        proposalCount++;
        Proposal memory newProposal = Proposal({
            id: proposalCount,
            proposer: msg.sender,
            eta: 0,
            targets: targets,
            values: values,
            signatures: signatures,
            calldatas: calldatas,
            startBlock: startBlock,
            endBlock: endBlock,
            forVotes: 0,
            againstVotes: 0,
            canceled: false,
            executed: false,
            quorumatPropose: quorum //UAs pheezez keeps growing, we do not want the proposal quorum to change while it is processing.
        });

        proposals[newProposal.id] = newProposal;
        latestProposalIds[newProposal.proposer] = newProposal.id;

        emit ProposalCreated(
            newProposal.id,
            msg.sender,
            targets,
            values,
            signatures,
            calldatas,
            startBlock,
            endBlock,
            description
        );
        treasury.sendReward(proposeReward, msg.sender);
        return newProposal.id;
    }

    function queue(uint256 proposalId) public {
        require(
            state(proposalId) == ProposalState.Succeeded,
            'GovernorAlpha::queue: proposal can only be queued if it is succeeded'
        );
        Proposal storage proposal = proposals[proposalId];
        uint256 eta = add256(block.timestamp, timelock.delay());
        for (uint256 i = 0; i < proposal.targets.length; i++) {
            _queueOrRevert(
                proposal.targets[i],
                proposal.values[i],
                proposal.signatures[i],
                proposal.calldatas[i],
                eta
            );
        }
        proposal.eta = eta;
        treasury.sendReward(queueReward, msg.sender);
        emit ProposalQueued(proposalId, eta);
    }

    function _queueOrRevert(
        address target,
        uint256 value,
        string memory signature,
        bytes memory data,
        uint256 eta
    ) internal {
        require(
            !timelock.queuedTransactions(
                keccak256(abi.encode(target, value, signature, data, eta))
            ),
            'GovernorAlpha::_queueOrRevert: proposal action already queued at eta'
        );
        timelock.queueTransaction(target, value, signature, data, eta);
    }

    function execute(uint256 proposalId) public payable {
        require(
            state(proposalId) == ProposalState.Queued,
            'GovernorAlpha::execute: proposal can only be executed if it is queued'
        );
        Proposal storage proposal = proposals[proposalId];
        proposal.executed = true;
        for (uint256 i = 0; i < proposal.targets.length; i++) {
            timelock.executeTransaction{value: proposal.values[i]}(
                proposal.targets[i],
                proposal.values[i],
                proposal.signatures[i],
                proposal.calldatas[i],
                proposal.eta
            );
        }
        treasury.sendReward(execReward, msg.sender);
        emit ProposalExecuted(proposalId);
    }

    function cancel(uint256 proposalId) public {
        ProposalState state = state(proposalId);
        require(
            state != ProposalState.Executed,
            'GovernorAlpha::cancel: cannot cancel executed proposal'
        );

        Proposal storage proposal = proposals[proposalId];
        require(
            msg.sender == guardian || proposal.proposer == msg.sender,
            'GovernorAlpha::cancel: Only the creator can cancel a proposal'
        ); //Guardian or Proposal creator can cancel.

        proposal.canceled = true;
        for (uint256 i = 0; i < proposal.targets.length; i++) {
            timelock.cancelTransaction(
                proposal.targets[i],
                proposal.values[i],
                proposal.signatures[i],
                proposal.calldatas[i],
                proposal.eta
            );
        }

        emit ProposalCanceled(proposalId);
    }

    function getActions(uint256 proposalId)
        public
        view
        returns (
            address[] memory targets,
            uint256[] memory values,
            string[] memory signatures,
            bytes[] memory calldatas
        )
    {
        Proposal storage p = proposals[proposalId];
        return (p.targets, p.values, p.signatures, p.calldatas);
    }

    function getReceipt(uint256 proposalId, address voter)
        public
        view
        returns (Receipt memory)
    {
        return proposals[proposalId].receipts[voter];
    }

    function state(uint256 proposalId) public view returns (ProposalState) {
        require(
            proposalCount >= proposalId && proposalId > 0,
            'GovernorAlpha::state: invalid proposal id'
        );
        Proposal storage proposal = proposals[proposalId];
        if (proposal.canceled) {
            return ProposalState.Canceled;
        } else if (block.number <= proposal.startBlock) {
            return ProposalState.Pending;
        } else if (block.number <= proposal.endBlock) {
            return ProposalState.Active;
        } else if (
            proposal.forVotes <= proposal.againstVotes ||
            proposal.forVotes < proposal.quorumatPropose
        ) {
            return ProposalState.Defeated;
        } else if (proposal.eta == 0) {
            return ProposalState.Succeeded;
        } else if (proposal.executed) {
            return ProposalState.Executed;
        } else if (
            block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD())
        ) {
            return ProposalState.Expired;
        } else {
            return ProposalState.Queued;
        }
    }

    function castVote(uint256 proposalId, bool support) public {
        return _castVote(msg.sender, proposalId, support);
    }

    function _castVote(
        address voter,
        uint256 proposalId,
        bool support
    ) internal {
        require(
            state(proposalId) == ProposalState.Active,
            'GovernorAlpha::_castVote: voting is closed'
        );
        Proposal storage proposal = proposals[proposalId];
        Receipt storage receipt = proposal.receipts[voter];
        require(
            receipt.hasVoted == false,
            'GovernorAlpha::_castVote: voter already voted'
        );
       
        uint256 votes = add256(pheezez.getPriorVotes(msg.sender, proposal.startBlock),
             poolPHZT.getPriorVotesinPool(msg.sender, proposal.startBlock));
        uint256 voteLimiter = SafeMath.div(
            SafeMath.mul(pheezez.votesAvailable(), vRatio),
            100
        );
        //In order to avoid whale unilateral voting.
        if (votes > voteLimiter) {
            votes = voteLimiter;
        }

        if (support) {
            proposal.forVotes = add256(proposal.forVotes, votes);
        } else {
            proposal.againstVotes = add256(proposal.againstVotes, votes);
        }

        receipt.hasVoted = true;
        receipt.support = support;
        receipt.votes = votes;
        treasury.sendReward(voteReward, msg.sender);
        emit VoteCast(voter, proposalId, support, votes);
    }

    function changeQuorumRatio(uint256 ratio) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::changeQuorumRatio: only the guardian can change the Ratio'
        );
        qRatio = ratio;
    }

    function changeThresholdRatio(uint256 ratio) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::changeThresholdRatio: only the guardian can change the Ratio'
        );
        tRatio = ratio;
    }

    function changeVotingRatio(uint256 ratio) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::changeVotingRatio: only the guardian can change the Ratio'
        );
        vRatio = ratio;
    }

    function changeVotingPeriod(uint256 period) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::changeVotingPeriod: only the guardian can change the Ratio'
        );
        vPeriod = period;
    }

    function changeRewards(uint256 vreward, uint256 qreward, uint256 ereward, uint256 preward) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::changeRewards: only the guardian can change the Ratio'
        );
        voteReward = vreward;
        queueReward = qreward;
        execReward = ereward;
        proposeReward = preward;
    }

    function setTreasury(address treasury_) external {
        require(
            msg.sender == guardian,
            'GovernorAlpha::setTreasury: only the guardian can do this action'
        );
        treasury = FRTTreasury(treasury_);
    }

    function __acceptAdmin() public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::__acceptAdmin: sender must be gov guardian'
        );
        timelock.acceptAdmin();
    }

    function __abdicate() public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::__abdicate: sender must be gov guardian'
        );
        guardian = address(0);
    }

    function __queueSetTimelockPendingAdmin(
        address newPendingAdmin,
        uint256 eta
    ) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian'
        );
        timelock.queueTransaction(
            address(timelock),
            0,
            'setPendingAdmin(address)',
            abi.encode(newPendingAdmin),
            eta
        );
    }

    function __executeSetTimelockPendingAdmin(
        address newPendingAdmin,
        uint256 eta
    ) public {
        require(
            msg.sender == guardian,
            'GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian'
        );
        timelock.executeTransaction(
            address(timelock),
            0,
            'setPendingAdmin(address)',
            abi.encode(newPendingAdmin),
            eta
        );
    }

    function add256(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, 'addition overflow');
        return c;
    }

    function sub256(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, 'subtraction underflow');
        return a - b;
    }
}

interface TimelockInterface {
    function delay() external view returns (uint256);

    function GRACE_PERIOD() external view returns (uint256);

    function acceptAdmin() external;

    function queuedTransactions(bytes32 hash) external view returns (bool);

    function queueTransaction(
        address target,
        uint256 value,
        string calldata signature,
        bytes calldata data,
        uint256 eta
    ) external returns (bytes32);

    function cancelTransaction(
        address target,
        uint256 value,
        string calldata signature,
        bytes calldata data,
        uint256 eta
    ) external;

    function executeTransaction(
        address target,
        uint256 value,
        string calldata signature,
        bytes calldata data,
        uint256 eta
    ) external payable returns (bytes memory);
}

interface PHEEZEZInterface {
    function getPriorVotes(address account, uint256 blockNumber)
        external
        view
        returns (uint256);

    function votesAvailable() external view returns (uint256);
}

interface FRTTreasury {
    function sendReward(uint256 rate, address account) external;
}
interface FRTPoolPHZT {
    function getPriorVotesinPool(address account, uint256 blockNumber)
        external
        view
        returns (uint256);
}