// Contract: Keep3r
// Target function: function permit(address owner, address spender, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external
, uint bond);

    /// @notice Keeper unbonding
    event KeeperUnbonding(address indexed keeper, uint block, uint deactive, uint bond);

    /// @notice Keeper unbound
    event KeeperUnbound(address indexed keeper, uint block, uint deactivated, uint bond);

    /// @notice Keeper slashed
    event KeeperSlashed(address indexed keeper, address indexed slasher, uint block, uint slash);

    /// @notice Keeper disputed
    event KeeperDispute(address indexed keeper, uint block);

    /// @notice Keeper resolved
    event KeeperResolved(address indexed keeper, uint block);

    /// @notice 1 day to bond to become a keeper
    uint constant public BOND = 3 days;
    /// @notice 14 days to unbond to remove funds from being a keeper
    uint constant public UNBOND = 14 days;
    /// @notice 7 days maximum downtime before being slashed
    uint constant public DOWNTIME = 7 days;

    /// @notice 5% of funds slashed for downtime
    uint constant public DOWNTIMESLASH = 500;
    uint constant public BASE = 10000;

    /// @notice tracks all current bondings (time)
    mapping(address => uint) public bondings;
    /// @notice tracks all current unbondings (time)
    mapping(address => uint) public unbondings;
    /// @notice allows for partial unbonding
    mapping(address => uint) public partialUnbonding;
    /// @notice tracks all current pending bonds (amount)
    mapping(address => uint) public pendingbonds;
    /// @notice tracks how much a keeper has bonded
    mapping(address => uint) public bonds;

    /// @notice total bonded (totalSupply for bonds)
    uint public totalBonded = 0;
    /// @notice tracks when a keeper was first registered
    mapping(address => uint) public firstSeen;

    /// @notice tracks if a keeper has a pending dispute
    mapping(address => bool) public disputes;

    /// @notice tracks last job performed for a keeper
    mapping(address => uint) public lastJob;
    /// @notice tracks the amount of job executions for a keeper
    mapping(address => uint) public work;
    /// @notice tracks the total job executions for a keeper
    mapping(address => uint) public workCompleted;
    /// @notice list of all jobs registered for the keeper system
    mapping(address => bool) public jobs;
    /// @notice the current credit available for a job
    mapping(address => uint) public credits;
    /// @notice the balances for the liquidity providers
    mapping(address => uint) public liquidityProviders;
    /// @notice tracks the relationship between a liquidityProvider and their job
    mapping(address => address) public liquidityProvided;
    /// @notice liquidity unbonding days
    mapping(address => uint) public liquidityUnbonding;
    /// @notice job proposal delay
    mapping(address => uint) public jobProposalDelay;
    /// @notice liquidity apply date
    mapping(address => uint) public liquidityApplied;

    /// @notice list of all current keepers
    mapping(address => bool) public keepers;
    /// @notice blacklist of keepers not allowed to participate
    mapping(address => bool) public blacklist;

    /// @notice traversable array of keepers to make external management easier
    address[] public keeperList;

    /// @notice governance address for the governance contract
    address public governance;
    address public pendingGovernance;

    /// @notice the liquidity token supplied by users paying for jobs
    UniswapPair public liquidity;
    
    uint internal gasUsed;
    
    constructor() public {
        // Set governance for this token
        governance = msg.sender;
        _mint(msg.sender, 10000e18);
    }

    function setup() public payable {
        require(address(liquidity) == address(0x0), "Keep3r::setup: keep3r already setup");
        WETH.deposit.value(msg.value)();
        WETH.approve(address(UNI), msg.value);
        _mint(address(this), 400e18);
        allowances[address(this)][address(UNI)] = balances[address(this)];

        // Setup liquidity pool with initial liquidity 1 ETH : 400 KPR
        UNI.addLiquidity(address(this), address(WETH), balances[address(this)], WETH.balanceOf(address(this)), 0, 0, msg.sender, now.add(1800));
        liquidity = UniswapPair(Factory(UNI.factory()).getPair(address(this), address(WETH)));
    }

    /**
     * @notice Allows liquidity providers to submit jobs
     * @param amount the amount of tokens to mint to treasury
     * @param job the job to assign credit to
     * @param amount the amount of liquidity tokens to use
     */
    function addLiquidityToJob(address job, uint amount) external {
        require(liquidityProvided[msg.sender]==address(0x0), "Keep3r::submitJob: liquidity already provided, please remove first");
        liquidity.transferFrom(msg.sender, address(this), amount);
        liquidityProviders[msg.sender] = amount;
        liquidityProvided[msg.sender] = job;
        liquidityApplied[msg.sender] = now.add(1 days);
        if (!jobs[job] && jobProposalDelay[job] < now) {
            Governance(governance).proposeJob(job);
            jobProposalDelay[job] = now.add(UNBOND);
        }
        emit SubmitJob(job, msg.sender, block.number, amount);
    }

    /**
     * @notice Applies the credit provided in addLiquidityToJob to the job
     * @param provider the liquidity provider
     */
    function applyCreditToJob(address provider) external {
        require(liquidityApplied[provider] != 0, "Keep3r::credit: submitJob first");
        require(liquidityApplied[provider] < now, "Keep3r::credit: still bonding");
        uint _liquidity = balances[address(liquidity)];
        uint _credit = _liquidity.mul(liquidityProviders[provider]).div(liquidity.totalSupply());
        credits[liquidityProvided[msg.sender]] = credits[liquidityProvided[msg.sender]].add(_credit);
    }

    /**
     * @notice Unbond liquidity for a pending keeper job
     */
    function unbondLiquidityFromJob() external {
        liquidityUnbonding[msg.sender] = now.add(UNBOND);

        emit UnbondJob(liquidityProvided[msg.sender], msg.sender, block.number, liquidityProviders[msg.sender]);
    }

    /**
     * @notice Allows liquidity providers to remove liquidity
     */
    function removeLiquidityFromJob() external {
        require(liquidityUnbonding[msg.sender] != 0, "Keep3r::removeJob: unbond first");
        require(liquidityUnbonding[msg.sender] < now, "Keep3r::removeJob: still unbonding");
        uint _provided = liquidityProviders[msg.sender];
        uint _liquidity = balances[address(liquidity)];
        uint _credit = _liquidity.mul(_provided).div(liquidity.totalSupply());
        address _job = liquidityProvided[msg.sender];
        if (_credit > credits[_job]) {
            credits[_job] = 0;
        } else {
            credits[_job].sub(_credit);
        }
        liquidity.transfer(msg.sender, _provided);
        liquidityProviders[msg.sender] = 0;
        liquidityProvided[msg.sender] = address(0x0);

        emit RemoveJob(_job, msg.sender, block.number, _provided);
    }

    /**
     * @notice Allows governance to mint new tokens to treasury
     * @param amount the amount of tokens to mint to treasury
     */
    function mint(uint amount) external {
        require(msg.sender == governance, "Keep3r::mint: governance only");
        _mint(governance, amount);
    }

    function burn(uint amount) external {
        _burn(msg.sender, amount);
    }

    function _mint(address dst, uint amount) internal {
        // mint the amount
        totalSupply = totalSupply.add(amount);
        // transfer the amount to the recipient
        balances[dst] = balances[dst].add(amount);
        emit Transfer(address(0), dst, amount);
    }

    function _burn(address dst, uint amount) internal {
        require(dst != address(0), "::_burn: burn from the zero address");
        balances[dst] = balances[dst].sub(amount, "::_burn: burn amount exceeds balance");
        totalSupply = totalSupply.sub(amount);
        emit Transfer(dst, address(0), amount);
    }

    /**
     * @notice Implemented by jobs to show that a keeper performend work
     * @param keeper address of the keeper that performed the work
     * @param amount the reward that should be allocated
     */
    function workReceipt(address keeper, uint amount) external {
        require(jobs[msg.sender], "Keep3r::workReceipt: only jobs can approve work");
        gasUsed = gasUsed.sub(gasleft());
        require(amount < KPRH.getQuoteLimit(gasUsed), "Keep3r::workReceipt: spending over max limit");
        lastJob[keeper] = now;
        credits[msg.sender] = credits[msg.sender].sub(amount, "Keep3r::workReceipt: insuffient funds to pay keeper");
        bonds[keeper] = bonds[keeper].add(amount);
        _moveDelegates(address(0), delegates[keeper], amount);
        workCompleted[keeper] = workCompleted[keeper].add(amount);
        emit KeeperWorked(msg.sender, keeper, block.number);
    }

    /**
     * @notice Allows governance to add new job systems
     * @param job address of the contract for which work should be performed
     */
    function addJob(address job) external {
        require(msg.sender == governance, "Keep3r::addJob: only governance can add jobs");
        jobs[job] = true;
        emit JobAdded(job, block.number, msg.sender);
    }

    /**
     * @notice Allows governance to remove a job from the systems
     * @param job address of the contract for which work should be performed
     */
    function removeJob(address job) external {
        require(msg.sender == governance, "Keep3r::removeJob: only governance can remove jobs");
        jobs[job] = false;
        emit JobRemoved(job, block.number, msg.sender);
    }

    /**
     * @notice Allows governance to change the Keep3rHelper for max spend
     * @param _kprh new helper address to set
     */
    function setKeep3rHelper(Keep3rHelper _kprh) external {
        require(msg.sender == governance, "Keep3r::setKeep3rHelper: only governance can set");
        KPRH = _kprh;
    }

    /**
     * @notice Allows governance to change governance (for future upgradability)
     * @param _governance new governance address to set
     */
    function setGovernance(address _governance) external {
        require(msg.sender == governance, "Keep3r::setGovernance: only governance can set");
        pendingGovernance = _governance;
    }
    
    /**
     * @notice Allows pendingGovernance to accept their role as governance (protection pattern)
     */
    function acceptGovernance() external {
        require(msg.sender == pendingGovernance, "Keep3r::acceptGovernance: only pendingGovernance can accept");
        governance = pendingGovernance;
    }

    /**
     * @notice confirms if the current keeper is registered, can be used for general (non critical) functions
     * @return true/false if the address is a keeper
     */
    function isKeeper(address keeper) external returns (bool) {
        gasUsed = gasleft();
        return keepers[keeper];
    }

    /**
     * @notice confirms if the current keeper is registered and has a minimum bond, should be used for protected functions
     * @return true/false if the address is a keeper and has more than the bond
     */
    function isMinKeeper(address keeper, uint minBond, uint completed, uint age) external returns (bool) {
        gasUsed = gasleft();
        return keepers[keeper]
                && bonds[keeper] >= minBond
                && workCompleted[keeper] > completed
                && now.sub(firstSeen[keeper]) > age;
    }

    /**
     * @notice begin the bonding process for a new keeper
     */
    function bond(uint amount) external {
        require(pendingbonds[msg.sender] == 0, "Keep3r::bond: current pending bond");
        require(blacklist[msg.sender], "Keep3r::bond: keeper is blacklisted");
        bondings[msg.sender] = now.add(BOND);
        pendingbonds[msg.sender] = amount;
        _transferTokens(msg.sender, address(this), amount);
        emit KeeperBonding(msg.sender, block.number, bondings[msg.sender], amount);
    }

    function getKeepers() external view returns (address[] memory) {
        return keeperList;
    }

    /**
     * @notice allows a keeper to activate/register themselves after bonding
     */
    function activate() external {
        require(bondings[msg.sender] != 0, "Keep3r::activate: bond first");
        require(bondings[msg.sender] < now, "Keep3r::activate: still bonding");
        if (!keepers[msg.sender]) {
          firstSeen[msg.sender] = now;
        }
        keepers[msg.sender] = true;
        totalBonded = totalBonded.add(pendingbonds[msg.sender]);
        bonds[msg.sender] = bonds[msg.sender].add(pendingbonds[msg.sender]);
        pendingbonds[msg.sender] = 0;
        if (lastJob[msg.sender] == 0) {
            lastJob[msg.sender] = now;
            keeperList.push(msg.sender);
        }
        emit KeeperBonded(msg.sender, block.number, block.timestamp, bonds[msg.sender]);
    }
    
    /**
     * @notice allows a keeper to deactivate (sub system to avoid down slashing)
     */
    function deactivate() external {
        keepers[msg.sender] = false;
    }

    /**
     * @notice begin the unbonding process to stop being a keeper
     * @param amount allows for partial unbonding
     */
    function unbond(uint amount) external {
        unbondings[msg.sender] = now.add(UNBOND);
        bonds[msg.sender] = bonds[msg.sender].sub(amount);
        partialUnbonding[msg.sender] = partialUnbonding[msg.sender].add(amount);
        totalBonded = totalBonded.sub(amount);
        _moveDelegates(delegates[msg.sender], address(0), amount);
        emit KeeperUnbonding(msg.sender, block.number, unbondings[msg.sender], amount);
    }

    /**
     * @notice withdraw funds after unbonding has finished
     */
    function withdraw() external {
        require(unbondings[msg.sender] != 0, "Keep3r::withdraw: unbond first");
        require(unbondings[msg.sender] < now, "Keep3r::withdraw: still unbonding");
        require(!disputes[msg.sender], "Keep3r::withdraw: pending disputes");

        _transferTokens(address(this), msg.sender, partialUnbonding[msg.sender]);
        emit KeeperUnbound(msg.sender, block.number, block.timestamp, partialUnbonding[msg.sender]);
        partialUnbonding[msg.sender] = 0;
    }

    /**
     * @notice slash a keeper for downtime
     * @param keeper the address being slashed
     */
    function down(address keeper) external {
        require(keepers[keeper], "Keep3r::down: keeper not registered");
        require(lastJob[keeper].add(DOWNTIME) < now, "Keep3r::down: keeper safe");
        uint _slash = bonds[keeper].mul(DOWNTIMESLASH).div(BASE);
        bonds[keeper] = bonds[keeper].sub(_slash);
        _moveDelegates(delegates[msg.sender], address(0), _slash);
        _transferTokens(address(this), msg.sender, _slash);
        lastJob[keeper] = now;
        emit KeeperSlashed(keeper, msg.sender, block.number, _slash);
    }

    /**
     * @notice allows governance to create a dispute for a given keeper
     * @param keeper the address in dispute
     */
    function dispute(address keeper) external returns (uint) {
        require(msg.sender == governance, "Keep3r::dispute: only governance can dispute");
        disputes[keeper] = true;
        emit KeeperDispute(keeper, block.number);
    }

    /**
     * @notice allows governance to slash a keeper based on a dispute
     * @param keeper the address being slashed
     * @param amount the amount being slashed
     */
    function slash(address keeper, uint amount) public {
        require(msg.sender == governance, "Keep3r::slash: only governance can resolve");
        _transferTokens(address(this), governance, amount);
        _moveDelegates(delegates[msg.sender], address(0), amount);
        bonds[keeper] = bonds[keeper].sub(amount);
        disputes[keeper] = false;
        emit KeeperSlashed(keeper, msg.sender, block.number, amount);
    }
    
    
    /**
     * @notice blacklists a keeper from participating in the network
     * @param keeper the address being slashed
     */
    function revoke(address keeper) external {
        require(msg.sender == governance, "Keep3r::slash: only governance can resolve");
        keepers[keeper] = false;
        blacklist[keeper] = true;
        slash(keeper, bonds[keeper]);
    }

    /**
     * @notice allows governance to resolve a dispute on a keeper
     * @param keeper the address cleared
     */
    function resolve(address keeper) external {
        require(msg.sender == governance, "Keep3r::resolve: only governance can resolve");
        disputes[keeper] = false;
        emit KeeperResolved(keeper, block.number);
    }

    /**
     * @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
     * @param account The address of the account holding the funds
     * @param spender The address of the account spending the funds
     * @return The number of tokens approved
     */
    function allowance(address account, address spender) external view returns (uint) {
        return allowances[account][spender];
    }

    /**
     * @notice Approve `spender` to transfer up to `amount` from `src`
     * @dev This will overwrite the approval amount for `spender`
     *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
     * @param spender The address of the account which may transfer tokens
     * @param amount The number of tokens that are approved (2^256-1 means infinite)
     * @return Whether or not the approval succeeded
     */
    function approve(address spender, uint amount) public returns (bool) {
        allowances[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);
        return true;
    }