// File: @openzeppelin/contracts/proxy/Clones.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/utils/Strings.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// File: @openzeppelin/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// File: @openzeppelin/contracts/utils/math/SignedMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// File: contracts/Tasks/Escrow.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Escrow {
    error AlreadyInitialized();
    error NotOwner();

    address private owner;

    /// @notice Initializes the Escrow with the sender of the transaction as owner.
    /// @dev This should be called in the same transaction as deploying the escrow, to prevent front running.
    function __Escrow_init() external {
        if (owner != address(0)) {
            revert AlreadyInitialized();
        }

        owner = msg.sender;
    }

    /// @notice Transfers a certain amount of ERC20 token to a given address. Can only be called by the owner.
    /// @param token The ERC20 contract address.
    /// @param to The address to recieve the tokens.
    /// @param amount The amount of ERC20 token to receive.
    function transfer(IERC20 token, address to, uint256 amount) external {
        if (msg.sender != owner) {
            revert NotOwner();
        }

        token.transfer(to, amount);
    }
}

// File: contracts/Tasks/ITasks.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Escrow} from "./Escrow.sol";

/*
  Some of the functionality in this contract will be removed in the next version.
  This functionality will be provided by off-chain indexing instead.
  Therefore this functionality might be implemented in a more dirty way to make it easier to remove.
  This is true for:
  - taskStatistics
  - getManagingTasks
  - getExecutingTasks
  - manager and executor/applicant in all events (except first introduction)

  Seperation of manager and creator is also a recent change. The frontend will currently use creator == manager.
  Hence there is also no getCreatedTasks.
*/
interface ITasks {
    error InvalidTimestamp();
    error InvalidAddress();
    error PointlessOperation();
    error ArrayLargerThanSupported();

    error TaskDoesNotExist();
    error TaskNotOpen();
    error TaskNotTaken();
    error TaskNotClosed();
    error TaskClosed();

    error NotManager();
    error NotExecutor();

    error RewardAboveBudget();
    error RewardDoesntEndWithNewToken();
    error ApplicationDoesNotExist();
    error NotYourApplication();
    error ApplicationNotAccepted();
    error SubmissionDoesNotExist();
    error SubmissionAlreadyJudged();
    error JudgementNone();

    error RequestDoesNotExist();
    error RequestAlreadyAccepted();
    error RequestNotAccepted();
    error RequestAlreadyExecuted();

    error ManualBudgetIncreaseRequired();

    event TaskCreated(
        uint256 indexed taskId,
        string metadata,
        uint64 deadline,
        ERC20Transfer[] budget,
        address creator,
        address manager
    );
    event ApplicationCreated(
        uint256 indexed taskId,
        uint16 applicationId,
        string metadata,
        Reward[] reward,
        address manager,
        address applicant
    );
    event ApplicationAccepted(
        uint256 indexed taskId,
        uint16 applicationId,
        address manager,
        address applicant
    );
    event TaskTaken(
        uint256 indexed taskId,
        uint16 applicationId,
        address manager,
        address executor
    );
    event SubmissionCreated(
        uint256 indexed taskId,
        uint8 submissionId,
        string metadata,
        address manager,
        address executor
    );
    event SubmissionReviewed(
        uint256 indexed taskId,
        uint8 submissionId,
        SubmissionJudgement judgement,
        string feedback,
        address manager,
        address executor
    );
    event TaskCompleted(
        uint256 indexed taskId,
        address manager,
        address executor
    );

    event CancelTaskRequested(
        uint256 indexed taskId,
        uint8 requestId,
        string explanation,
        address manager,
        address executor
    );
    event TaskCancelled(
        uint256 indexed taskId,
        address manager,
        address executor
    );
    event RequestAccepted(
        uint256 indexed taskId,
        RequestType requestType,
        uint8 requestId,
        address manager,
        address executor
    );
    event RequestExecuted(
        uint256 indexed taskId,
        RequestType requestType,
        uint8 requestId,
        address by,
        address manager,
        address executor
    );

    event DeadlineExtended(
        uint256 indexed taskId,
        uint64 extension,
        address manager,
        address executor
    );
    event BudgetIncreased(
        uint256 indexed taskId,
        uint96[] increase,
        address manager
    );
    event MetadataEditted(
        uint256 indexed taskId,
        string newMetadata,
        address manager
    );

    /// @notice A container for ERC20 transfer information.
    /// @param tokenContract ERC20 token to transfer.
    /// @param amount How much of this token should be transfered.
    struct ERC20Transfer {
        IERC20 tokenContract;
        uint96 amount;
    }

    /// @notice A container for a reward payout.
    /// @param nextToken If this reward is payed out in the next ERC20 token.
    /// @dev IERC20 (address) is a lot of storage, rather just keep those only in budget.
    /// @notice nextToken should always be true for the last entry
    /// @param to Whom this token should be transfered to.
    /// @param amount How much of this token should be transfered.
    struct Reward {
        bool nextToken;
        address to;
        uint88 amount;
    }

    /// @notice A container for a task application.
    /// @param metadata Metadata of the application. (IPFS hash)
    /// @param applicant Who has submitted this application.
    /// @param accepted If the application has been accepted by the manager.
    /// @param reward How much rewards the applicant wants for completion.
    struct Application {
        string metadata;
        address applicant;
        bool accepted;
        uint8 rewardCount;
        mapping(uint8 => Reward) reward;
    }

    struct OffChainApplication {
        string metadata;
        address applicant;
        bool accepted;
        Reward[] reward;
    }

    /// @notice For approving people on task creation (they are not required to make an application)
    struct PreapprovedApplication {
        address applicant;
        Reward[] reward;
    }

    enum SubmissionJudgement {
        None,
        Accepted,
        Rejected
    }
    /// @notice A container for a task submission.
    /// @param metadata Metadata of the submission. (IPFS hash)
    /// @param judgement Judgement cast on the submission.
    /// @param feedback A response from the manager. (IPFS hash)
    struct Submission {
        string metadata;
        string feedback;
        SubmissionJudgement judgement;
    }

    enum RequestType {
        CancelTask
    }

    /// @notice A container for shared request information.
    /// @param accepted If the request was accepted.
    /// @param executed If the request was executed.
    struct Request {
        bool accepted;
        bool executed;
    }

    /// @notice A container for a request to cancel the task.
    /// @param request Request information.
    /// @param explanation Why the task should be cancelled.
    struct CancelTaskRequest {
        Request request;
        string explanation;
    }

    enum TaskState {
        Open,
        Taken,
        Closed
    }
    /// @notice A container for task-related information.
    /// @param metadata Metadata of the task. (IPFS hash)
    /// @param deadline Block timestamp at which the task expires if not completed.
    /// @param budget Maximum ERC20 rewards that can be earned by completing the task.
    /// @param manager Who has created the task.
    /// @param state Current state the task is in.
    /// @param applications Applications to take the job.
    /// @param executorApplication Index of the application that will execture the task.
    /// @param submissions Submission made to finish the task.
    /// @dev Storage blocks seperated by newlines.
    struct Task {
        string metadata;
        uint64 deadline;
        Escrow escrow;
        address creator;
        address manager;
        TaskState state;
        uint16 executorApplication;
        uint8 budgetCount;
        uint16 applicationCount;
        uint8 submissionCount;
        uint8 cancelTaskRequestCount;
        mapping(uint8 => ERC20Transfer) budget;
        mapping(uint16 => Application) applications;
        mapping(uint8 => Submission) submissions;
        mapping(uint8 => CancelTaskRequest) cancelTaskRequests;
    }

    struct OffChainTask {
        string metadata;
        uint64 deadline;
        uint16 executorApplication;
        address creator;
        address manager;
        TaskState state;
        Escrow escrow;
        ERC20Transfer[] budget;
        OffChainApplication[] applications;
        Submission[] submissions;
        CancelTaskRequest[] cancelTaskRequests;
    }

    /// @notice Retrieves the current amount of created tasks.
    function taskCount() external view returns (uint256);

    /// @notice Retrieves the current statistics of created tasks.
    function taskStatistics()
        external
        view
        returns (
            uint256 openTasks,
            uint256 takenTasks,
            uint256 successfulTasks
        );

    /// @notice Retrieves all task information by id.
    /// @param _taskId Id of the task.
    function getTask(
        uint256 _taskId
    ) external view returns (OffChainTask memory);

    /// @notice Retrieves multiple tasks.
    /// @param _taskIds Ids of the tasks.
    function getTasks(
        uint256[] calldata _taskIds
    ) external view returns (OffChainTask[] memory);

    /// @notice Retrieves all tasks of a manager. Most recent ones first.
    /// @param _manager The manager to fetch tasks of.
    /// @param _fromTaskId What taskId to start from. 0 for most recent task.
    /// @param _max The maximum amount of tasks to return. 0 for no max.
    function getManagingTasks(
        address _manager,
        uint256 _fromTaskId,
        uint256 _max
    ) external view returns (OffChainTask[] memory);

    /// @notice Retrieves all tasks of an executor. Most recent ones first.
    /// @param _executor The executor to fetch tasks of.
    /// @param _fromTaskId What taskId to start from. 0 for most recent task.
    /// @param _max The maximum amount of tasks to return. 0 for no max.
    function getExecutingTasks(
        address _executor,
        uint256 _fromTaskId,
        uint256 _max
    ) external view returns (OffChainTask[] memory);

    /// @notice Create a new task.
    /// @param _metadata Metadata of the task. (IPFS hash)
    /// @param _deadline Block timestamp at which the task expires if not completed.
    /// @param _budget Maximum ERC20 rewards that can be earned by completing the task.
    /// @param _manager Who will manage the task (become the manager).
    /// @return taskId Id of the newly created task.
    function createTask(
        string calldata _metadata,
        uint64 _deadline,
        ERC20Transfer[] calldata _budget,
        address _manager,
        PreapprovedApplication[] calldata _preapprove
    ) external returns (uint256 taskId);

    /// @notice Apply to take the task.
    /// @param _taskId Id of the task.
    /// @param _metadata Metadata of your application.
    /// @param _reward Wanted rewards for completing the task.
    function applyForTask(
        uint256 _taskId,
        string calldata _metadata,
        Reward[] calldata _reward
    ) external returns (uint16 applicationId);

    /// @notice Accept application to allow them to take the task.
    /// @param _taskId Id of the task.
    /// @param _applicationIds Indexes of the applications to accept.
    function acceptApplications(
        uint256 _taskId,
        uint16[] calldata _applicationIds
    ) external;

    /// @notice Take the task after your application has been accepted.
    /// @param _taskId Id of the task.
    /// @param _applicationId Index of application you made that has been accepted.
    function takeTask(uint256 _taskId, uint16 _applicationId) external;

    /// @notice Create a submission.
    /// @param _taskId Id of the task.
    /// @param _metadata Metadata of the submission. (IPFS hash)
    function createSubmission(
        uint256 _taskId,
        string calldata _metadata
    ) external returns (uint8 submissionId);

    /// @notice Review a submission.
    /// @param _taskId Id of the task.
    /// @param _submissionId Index of the submission that is reviewed.
    /// @param _judgement Outcome of the review.
    /// @param _feedback Reasoning of the reviewer. (IPFS hash)
    function reviewSubmission(
        uint256 _taskId,
        uint8 _submissionId,
        SubmissionJudgement _judgement,
        string calldata _feedback
    ) external;

    /// @notice Cancels a task. This can be used to close a task and receive back the budget.
    /// @param _taskId Id of the task.
    /// @param _explanation Why the task was cancelled. (IPFS hash)
    function cancelTask(
        uint256 _taskId,
        string calldata _explanation
    ) external returns (uint8 cancelTaskRequestId);

    /// @notice Accepts a request, executing the proposed action.
    /// @param _taskId Id of the task.
    /// @param _requestType What kind of request it is.
    /// @param _requestId Id of the request.
    /// @param _execute If the request should also be executed in this transaction.
    function acceptRequest(
        uint256 _taskId,
        RequestType _requestType,
        uint8 _requestId,
        bool _execute
    ) external;

    /// @notice Exectued an accepted request, allows anyone to pay for the gas costs of the execution.
    /// @param _taskId Id of the task.
    /// @param _requestType What kind of request it is.
    /// @param _requestId Id of the request.
    function executeRequest(
        uint256 _taskId,
        RequestType _requestType,
        uint8 _requestId
    ) external;

    /// @notice Extend the deadline of a task.
    /// @param _taskId Id of the task.
    /// @param _extension How much to extend the deadline by.
    function extendDeadline(uint256 _taskId, uint64 _extension) external;

    /// @notice Increase the budget of the task.
    /// @param _taskId Id of the task.
    /// @param _increase How much to increase each tokens amount by.
    function increaseBudget(
        uint256 _taskId,
        uint96[] calldata _increase
    ) external;

    /// @notice Edit the metadata of a task.
    /// @param _taskId Id of the task.
    /// @param _newMetadata New metadata of the task.
    /// @dev This metadata update might change the task completely. Show a warning to people who applied before the change.
    function editMetadata(
        uint256 _taskId,
        string calldata _newMetadata
    ) external;
}

// File: contracts/Tasks/Tasks.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ITasks, IERC20, Escrow} from "./ITasks.sol";
import {TasksEnsure} from "./TasksEnsure.sol";
import {TasksUtils} from "./TasksUtils.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

contract Tasks is Context, TasksEnsure, TasksUtils {
    /// @notice The incremental ID for tasks.
    uint256 private taskCounter;

    /// @notice Various statistics about total tasks.
    uint256 private openTasks;
    uint256 private takenTasks;
    uint256 private successfulTasks;

    /// @notice A mapping between task IDs and task information.
    mapping(uint256 => Task) internal tasks;

    /// @notice The base escrow contract that will be cloned for every task.
    address private immutable escrowImplementation;

    /// @notice This address has the power to disable the contract, in case an exploit is discovered.
    address private disabler;
    error Disabled();
    error NotDisabled();
    error NotDisabler();

    constructor() {
        escrowImplementation = address(new Escrow());
        disabler = _msgSender();
    }

    /// @inheritdoc ITasks
    function taskCount() external view returns (uint256) {
        return taskCounter;
    }

    /// @inheritdoc ITasks
    function taskStatistics()
        external
        view
        returns (uint256 open, uint256 taken, uint256 successful)
    {
        (open, taken, successful) = (openTasks, takenTasks, successfulTasks);
    }

    /// @inheritdoc ITasks
    function getTask(
        uint256 _taskId
    ) public view returns (OffChainTask memory offchainTask) {
        Task storage task = _getTask(_taskId);
        offchainTask = _toOffchainTask(task);
    }

    /// @inheritdoc ITasks
    function getTasks(
        uint256[] memory _taskIds
    ) public view returns (OffChainTask[] memory) {
        OffChainTask[] memory offchainTasks = new OffChainTask[](
            _taskIds.length
        );
        for (uint i; i < _taskIds.length; ) {
            offchainTasks[i] = getTask(_taskIds[i]);

            unchecked {
                ++i;
            }
        }
        return offchainTasks;
    }

    /// @inheritdoc ITasks
    function getManagingTasks(
        address _manager,
        uint256 _fromTaskId,
        uint256 _max
    ) external view returns (OffChainTask[] memory) {
        uint256 totalTasks = taskCounter;
        uint256[] memory taskIndexes = new uint256[](totalTasks);
        uint256 managerTasksCount;
        if (_fromTaskId == 0) {
            _fromTaskId = totalTasks - 1;
        }
        for (uint256 i = _fromTaskId; i != type(uint256).max; ) {
            if (tasks[i].manager == _manager) {
                taskIndexes[managerTasksCount] = i;
                unchecked {
                    ++managerTasksCount;
                }
                if (managerTasksCount == _max) {
                    // _max == 0 never triggering is on purpose
                    break;
                }
            }

            unchecked {
                --i;
            }
        }
        // decrease length of array to match real entries
        assembly {
            mstore(
                taskIndexes,
                sub(mload(taskIndexes), sub(totalTasks, managerTasksCount))
            )
        }
        return getTasks(taskIndexes);
    }

    /// @inheritdoc ITasks
    function getExecutingTasks(
        address _executor,
        uint256 _fromTaskId,
        uint256 _max
    ) external view returns (OffChainTask[] memory) {
        uint256 totalTasks = taskCounter;
        uint256[] memory taskIndexes = new uint256[](totalTasks);
        uint256 executorTasksCount;
        if (_fromTaskId == 0) {
            _fromTaskId = totalTasks - 1;
        }
        for (uint256 i = _fromTaskId; i != type(uint256).max; ) {
            if (
                tasks[i].state != TaskState.Open &&
                tasks[i].applications[tasks[i].executorApplication].applicant ==
                _executor
            ) {
                taskIndexes[executorTasksCount] = i;
                unchecked {
                    ++executorTasksCount;
                }
                if (executorTasksCount == _max) {
                    // _max == 0 never triggering is on purpose
                    break;
                }
            }

            unchecked {
                --i;
            }
        }
        // decrease length of array to match real entries
        assembly {
            mstore(
                taskIndexes,
                sub(mload(taskIndexes), sub(totalTasks, executorTasksCount))
            )
        }
        return getTasks(taskIndexes);
    }

    /// @inheritdoc ITasks
    function createTask(
        string calldata _metadata,
        uint64 _deadline,
        ERC20Transfer[] calldata _budget,
        address _manager,
        PreapprovedApplication[] calldata _preapprove
    ) external returns (uint256 taskId) {
        _ensureValidTimestamp(_deadline);
        _ensureValidAddress(_manager);
        if (_budget.length >= type(uint8).max) {
            revert ArrayLargerThanSupported();
        }
        if (_preapprove.length >= type(uint16).max) {
            revert ArrayLargerThanSupported();
        }

        _ensureNotDisabled();
        taskId = taskCounter++;

        Task storage task = tasks[taskId];
        task.metadata = _metadata;
        task.deadline = _deadline;
        task.budgetCount = uint8(_budget.length);
        Escrow escrow = Escrow(Clones.clone(escrowImplementation));
        escrow.__Escrow_init();
        task.escrow = escrow;
        for (uint8 i; i < uint8(_budget.length); ) {
            _budget[i].tokenContract.transferFrom(
                _msgSender(),
                address(escrow),
                _budget[i].amount
            );

            // use balanceOf in case there is a fee asoosiated with the transfer
            task.budget[i] = ERC20Transfer(
                _budget[i].tokenContract,
                uint96(_budget[i].tokenContract.balanceOf(address(escrow)))
            );

            unchecked {
                ++i;
            }
        }

        task.manager = _manager;
        task.creator = _msgSender();

        // Default values are already correct (save gas)
        // task.state = TaskState.Open;
        unchecked {
            // Impossible to overflow due to openTasks <= taskCounter
            ++openTasks;
        }

        emit TaskCreated(
            taskId,
            _metadata,
            _deadline,
            _budget,
            _msgSender(),
            _manager
        );

        // Gas optimization
        if (_preapprove.length > 0) {
            task.applicationCount = uint16(_preapprove.length);
            for (uint16 i; i < uint16(_preapprove.length); ) {
                Application storage application = task.applications[i];
                application.applicant = _preapprove[i].applicant;
                application.accepted = true;
                _ensureRewardEndsWithNextToken(_preapprove[i].reward);
                _setRewardBellowBudget(
                    task,
                    application,
                    _preapprove[i].reward
                );

                emit ApplicationCreated(
                    taskId,
                    i,
                    "",
                    _preapprove[i].reward,
                    _manager,
                    _preapprove[i].applicant
                );

                emit ApplicationAccepted(
                    taskId,
                    i,
                    _manager,
                    _preapprove[i].applicant
                );

                unchecked {
                    ++i;
                }
            }
        }
    }

    /// @inheritdoc ITasks
    function applyForTask(
        uint256 _taskId,
        string calldata _metadata,
        Reward[] calldata _reward
    ) external returns (uint16 applicationId) {
        if (_reward.length >= type(uint8).max) {
            revert ArrayLargerThanSupported();
        }

        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsOpen(task);
        _ensureRewardEndsWithNextToken(_reward);

        Application storage application = task.applications[
            task.applicationCount
        ];
        application.metadata = _metadata;
        application.applicant = _msgSender();
        application.rewardCount = uint8(_reward.length);
        for (uint8 i; i < uint8(_reward.length); ) {
            application.reward[i] = _reward[i];
            unchecked {
                ++i;
            }
        }

        applicationId = task.applicationCount++;

        emit ApplicationCreated(
            _taskId,
            applicationId,
            _metadata,
            _reward,
            task.manager,
            _msgSender()
        );
    }

    /// @inheritdoc ITasks
    function acceptApplications(
        uint256 _taskId,
        uint16[] calldata _applicationIds
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsOpen(task);
        _ensureSenderIsManager(task);

        for (uint i; i < _applicationIds.length; ) {
            _ensureApplicationExists(task, _applicationIds[i]);

            Application storage application = task.applications[
                _applicationIds[i]
            ];
            application.accepted = true;
            _increaseBudgetToReward(
                task,
                application.rewardCount,
                application.reward
            );
            emit ApplicationAccepted(
                _taskId,
                _applicationIds[i],
                _msgSender(),
                application.applicant
            );

            unchecked {
                ++i;
            }
        }
    }

    /// @inheritdoc ITasks
    function takeTask(uint256 _taskId, uint16 _applicationId) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsOpen(task);
        _ensureApplicationExists(task, _applicationId);

        Application storage application = task.applications[_applicationId];
        _ensureSenderIsApplicant(application);
        _ensureApplicationIsAccepted(application);

        task.executorApplication = _applicationId;

        unchecked {
            --openTasks;
            ++takenTasks;
        }
        task.state = TaskState.Taken;

        emit TaskTaken(_taskId, _applicationId, task.manager, _msgSender());
    }

    /// @inheritdoc ITasks
    function createSubmission(
        uint256 _taskId,
        string calldata _metadata
    ) external returns (uint8 submissionId) {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsTaken(task);
        _ensureSenderIsExecutor(task);

        Submission storage submission = task.submissions[task.submissionCount];
        submission.metadata = _metadata;
        submissionId = task.submissionCount++;

        emit SubmissionCreated(
            _taskId,
            submissionId,
            _metadata,
            task.manager,
            _msgSender()
        );
    }

    /// @inheritdoc ITasks
    function reviewSubmission(
        uint256 _taskId,
        uint8 _submissionId,
        SubmissionJudgement _judgement,
        string calldata _feedback
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsTaken(task);
        _ensureSenderIsManager(task);
        _ensureSubmissionExists(task, _submissionId);

        Submission storage submission = task.submissions[_submissionId];
        _ensureSubmissionNotJudged(submission);
        _ensureJudgementNotNone(_judgement);
        submission.judgement = _judgement;
        submission.feedback = _feedback;

        if (_judgement == SubmissionJudgement.Accepted) {
            unchecked {
                --takenTasks;
                ++successfulTasks;
            }
            _payoutTask(task);

            emit TaskCompleted(
                _taskId,
                _msgSender(),
                task.applications[task.executorApplication].applicant
            );
        }

        emit SubmissionReviewed(
            _taskId,
            _submissionId,
            _judgement,
            _feedback,
            _msgSender(),
            task.applications[task.executorApplication].applicant
        );
    }

    /// @inheritdoc ITasks
    function cancelTask(
        uint256 _taskId,
        string calldata _explanation
    ) external returns (uint8 cancelTaskRequestId) {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureSenderIsManager(task);

        _ensureTaskNotClosed(task);

        if (
            task.state == TaskState.Open ||
            task.deadline <= uint64(block.timestamp)
        ) {
            // Task is open or deadline past
            if (task.state == TaskState.Open) {
                unchecked {
                    --openTasks;
                }
            } else {
                // if (task.state == TaskState.Taken) {
                unchecked {
                    --takenTasks;
                }
            }
            _refundCreator(task);

            emit TaskCancelled(
                _taskId,
                _msgSender(),
                task.state == TaskState.Open
                    ? address(0)
                    : task.applications[task.executorApplication].applicant
            );
            // Max means no request
            cancelTaskRequestId = type(uint8).max;
        } else {
            // Task is taken and deadline has not past
            CancelTaskRequest storage request = task.cancelTaskRequests[
                task.cancelTaskRequestCount
            ];
            request.explanation = _explanation;
            cancelTaskRequestId = task.cancelTaskRequestCount++;

            emit CancelTaskRequested(
                _taskId,
                cancelTaskRequestId,
                _explanation,
                _msgSender(),
                task.applications[task.executorApplication].applicant
            );
        }
    }

    /// @inheritdoc ITasks
    function acceptRequest(
        uint256 _taskId,
        RequestType _requestType,
        uint8 _requestId,
        bool _execute
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsTaken(task);
        _ensureSenderIsExecutor(task);

        //if (_requestType == RequestType.CancelTask) {
        {
            _ensureCancelTaskRequestExists(task, _requestId);

            CancelTaskRequest storage cancelTaskRequest = task
                .cancelTaskRequests[_requestId];
            _ensureRequestNotAccepted(cancelTaskRequest.request);

            if (_execute) {
                // use executeRequest in the body instead? (more gas due to all the checks, but less code duplication)
                unchecked {
                    --takenTasks;
                }
                _refundCreator(task);

                emit TaskCancelled(_taskId, task.manager, _msgSender());
                cancelTaskRequest.request.executed = true;
            }

            cancelTaskRequest.request.accepted = true;
        }

        emit RequestAccepted(
            _taskId,
            _requestType,
            _requestId,
            task.manager,
            _msgSender()
        );
    }

    /// @inheritdoc ITasks
    function executeRequest(
        uint256 _taskId,
        RequestType _requestType,
        uint8 _requestId
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskIsTaken(task);

        //if (_requestType == RequestType.CancelTask) {
        {
            _ensureCancelTaskRequestExists(task, _requestId);

            CancelTaskRequest storage cancelTaskRequest = task
                .cancelTaskRequests[_requestId];
            _ensureRequestAccepted(cancelTaskRequest.request);
            _ensureRequestNotExecuted(cancelTaskRequest.request);

            unchecked {
                --takenTasks;
            }
            _refundCreator(task);

            emit TaskCancelled(
                _taskId,
                task.manager,
                task.applications[task.executorApplication].applicant
            );
            cancelTaskRequest.request.executed = true;
        }

        emit RequestExecuted(
            _taskId,
            _requestType,
            _requestId,
            _msgSender(),
            task.manager,
            task.applications[task.executorApplication].applicant
        );
    }

    /// @inheritdoc ITasks
    function extendDeadline(uint256 _taskId, uint64 _extension) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureSenderIsManager(task);

        _ensureTaskNotClosed(task);

        if (_extension == 0) {
            revert PointlessOperation();
        }
        task.deadline += _extension;

        emit DeadlineExtended(
            _taskId,
            _extension,
            _msgSender(),
            task.state == TaskState.Open
                ? address(0)
                : task.applications[task.executorApplication].applicant
        );
    }

    /// @inheritdoc ITasks
    function increaseBudget(
        uint256 _taskId,
        uint96[] calldata _increase
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureSenderIsManager(task);

        _ensureTaskIsOpen(task);

        for (uint8 i; i < uint8(_increase.length); ) {
            ERC20Transfer storage transfer = task.budget[i];
            transfer.tokenContract.transferFrom(
                _msgSender(),
                address(task.escrow),
                _increase[i]
            );

            // Use balanceOf as there could be a fee in transferFrom
            transfer.amount = uint96(
                transfer.tokenContract.balanceOf(address(task.escrow))
            );

            unchecked {
                ++i;
            }
        }

        emit BudgetIncreased(_taskId, _increase, _msgSender());
    }

    /// @inheritdoc ITasks
    function editMetadata(
        uint256 _taskId,
        string calldata _newMetadata
    ) external {
        _ensureNotDisabled();
        Task storage task = _getTask(_taskId);
        _ensureSenderIsManager(task);

        _ensureTaskIsOpen(task);

        if (Strings.equal(task.metadata, _newMetadata)) {
            revert PointlessOperation();
        }
        task.metadata = _newMetadata;
        emit MetadataEditted(_taskId, _newMetadata, _msgSender());
    }

    function disable() external {
        _ensureDisabler();
        disabler = address(0);
    }

    // Ideally you are able to transfer it to the new contract, but that requires addition to the escrow contract
    // I prefer this, so the escrow contract keeps being basic (both for security and clone costs)
    function refund(uint256 _taskId) external {
        _ensureDisabled();
        Task storage task = _getTask(_taskId);
        _ensureTaskNotClosed(task);
        // oficially should update taskOpen / Taken here, but as the contract will cease operations, no point
        _refundCreator(task);
    }

    function _getTask(
        uint256 _taskId
    ) internal view returns (Task storage task) {
        if (_taskId >= taskCounter) {
            revert TaskDoesNotExist();
        }

        task = tasks[_taskId];
    }

    function _ensureNotDisabled() internal view {
        if (disabler == address(0)) {
            revert Disabled();
        }
    }

    function _ensureDisabled() internal view {
        if (disabler != address(0)) {
            revert NotDisabled();
        }
    }

    function _ensureDisabler() internal view {
        if (_msgSender() != disabler) {
            revert NotDisabler();
        }
    }
}

// File: contracts/Tasks/TasksEnsure.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ITasks, Escrow} from "./ITasks.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";

/*
  Functions to ensure a certain precondition is met.
*/
abstract contract TasksEnsure is ITasks, Context {
    function _ensureValidTimestamp(uint64 timestamp) internal pure {
        if (timestamp == 0) {
            revert InvalidTimestamp();
        }
    }

    function _ensureValidAddress(address addr) internal pure {
        if (addr == address(0)) {
            revert InvalidAddress();
        }
    }

    function _ensureTaskIsOpen(Task storage task) internal view {
        if (task.state != TaskState.Open) {
            revert TaskNotOpen();
        }
    }

    function _ensureTaskIsTaken(Task storage task) internal view {
        if (task.state != TaskState.Taken) {
            revert TaskNotTaken();
        }
    }

    function _ensureTaskNotClosed(Task storage task) internal view {
        if (task.state == TaskState.Closed) {
            revert TaskClosed();
        }
    }

    function _ensureSenderIsManager(Task storage task) internal view {
        if (task.manager != _msgSender()) {
            revert NotManager();
        }
    }

    ///@dev Should only be called is the task is not open!
    function _ensureSenderIsExecutor(Task storage task) internal view {
        if (
            task.applications[task.executorApplication].applicant !=
            _msgSender()
        ) {
            revert NotExecutor();
        }
    }

    function _ensureRewardEndsWithNextToken(
        Reward[] memory reward
    ) internal pure {
        unchecked {
            if (reward.length != 0 && !reward[reward.length - 1].nextToken) {
                revert RewardDoesntEndWithNewToken();
            }
        }
    }

    function _ensureApplicationExists(
        Task storage task,
        uint16 _applicationId
    ) internal view {
        if (_applicationId >= task.applicationCount) {
            revert ApplicationDoesNotExist();
        }
    }

    function _ensureSenderIsApplicant(
        Application storage application
    ) internal view {
        if (application.applicant != _msgSender()) {
            revert NotYourApplication();
        }
    }

    function _ensureApplicationIsAccepted(
        Application storage application
    ) internal view {
        if (!application.accepted) {
            revert ApplicationNotAccepted();
        }
    }

    function _ensureSubmissionExists(
        Task storage task,
        uint8 _submissionId
    ) internal view {
        if (_submissionId >= task.submissionCount) {
            revert SubmissionDoesNotExist();
        }
    }

    function _ensureSubmissionNotJudged(
        Submission storage submission
    ) internal view {
        if (submission.judgement != SubmissionJudgement.None) {
            revert SubmissionAlreadyJudged();
        }
    }

    function _ensureJudgementNotNone(
        SubmissionJudgement judgement
    ) internal pure {
        if (judgement == SubmissionJudgement.None) {
            revert JudgementNone();
        }
    }

    function _ensureCancelTaskRequestExists(
        Task storage task,
        uint8 _requestId
    ) internal view {
        if (_requestId >= task.cancelTaskRequestCount) {
            revert RequestDoesNotExist();
        }
    }

    function _ensureRequestNotAccepted(Request storage request) internal view {
        if (request.accepted) {
            revert RequestAlreadyAccepted();
        }
    }

    function _ensureRequestAccepted(Request storage request) internal view {
        if (!request.accepted) {
            revert RequestNotAccepted();
        }
    }

    function _ensureRequestNotExecuted(Request storage request) internal view {
        if (request.executed) {
            revert RequestAlreadyExecuted();
        }
    }
}

// File: contracts/Tasks/TasksUtils.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ITasks, Escrow} from "./ITasks.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";

/*
  Higher level functions to allow the Tasks file to be more readable.
*/
abstract contract TasksUtils is ITasks, Context {
    function _toOffchainTask(
        Task storage task
    ) internal view returns (OffChainTask memory offchainTask) {
        offchainTask.metadata = task.metadata;
        offchainTask.deadline = task.deadline;
        offchainTask.executorApplication = task.executorApplication;
        offchainTask.creator = task.creator;
        offchainTask.manager = task.manager;
        offchainTask.state = task.state;
        offchainTask.escrow = task.escrow;

        offchainTask.budget = new ERC20Transfer[](task.budgetCount);
        for (uint8 i; i < offchainTask.budget.length; ) {
            offchainTask.budget[i] = task.budget[i];
            unchecked {
                ++i;
            }
        }

        offchainTask.applications = new OffChainApplication[](
            task.applicationCount
        );
        for (uint8 i; i < offchainTask.applications.length; ) {
            Application storage application = task.applications[i];
            offchainTask.applications[i].metadata = application.metadata;
            offchainTask.applications[i].applicant = application.applicant;
            offchainTask.applications[i].accepted = application.accepted;
            offchainTask.applications[i].reward = new Reward[](
                application.rewardCount
            );
            for (uint8 j; j < offchainTask.applications[i].reward.length; ) {
                offchainTask.applications[i].reward[j] = application.reward[j];
                unchecked {
                    ++j;
                }
            }
            unchecked {
                ++i;
            }
        }

        offchainTask.submissions = new Submission[](task.submissionCount);
        for (uint8 i; i < offchainTask.submissions.length; ) {
            offchainTask.submissions[i] = task.submissions[i];
            unchecked {
                ++i;
            }
        }

        offchainTask.cancelTaskRequests = new CancelTaskRequest[](
            task.cancelTaskRequestCount
        );
        for (uint8 i; i < offchainTask.cancelTaskRequests.length; ) {
            offchainTask.cancelTaskRequests[i] = task.cancelTaskRequests[i];
            unchecked {
                ++i;
            }
        }
    }

    function _increaseBudgetToReward(
        Task storage task,
        uint8 _length,
        mapping(uint8 => Reward) storage _reward
    ) internal {
        uint8 j;
        ERC20Transfer memory erc20Transfer = task.budget[0];
        uint256 needed;
        for (uint8 i; i < _length; ) {
            unchecked {
                needed += _reward[i].amount;
            }

            if (_reward[i].nextToken) {
                if (needed > erc20Transfer.amount) {
                    // Existing budget in escrow doesnt cover the needed reward
                    erc20Transfer.tokenContract.transferFrom(
                        _msgSender(),
                        address(task.escrow),
                        needed - erc20Transfer.amount
                    );

                    if (
                        erc20Transfer.tokenContract.balanceOf(
                            address(task.escrow)
                        ) < needed
                    ) {
                        // transferFrom returns less funds than send, use increaseBudget to increase balance to wanted reward.
                        revert ManualBudgetIncreaseRequired();
                    }

                    // (2^88-1) * 2^8 = 2^96-2^8, needed cannot be more than 2^96-1, so fits in uint96
                    task.budget[j].amount = uint96(needed);
                }

                needed = 0;
                unchecked {
                    erc20Transfer = task.budget[++j];
                }
            }

            unchecked {
                ++i;
            }
        }
    }

    function _setRewardBellowBudget(
        Task storage task,
        Application storage application,
        Reward[] calldata _reward
    ) internal {
        if (_reward.length >= type(uint8).max) {
            revert ArrayLargerThanSupported();
        }
        application.rewardCount = uint8(_reward.length);

        uint8 j;
        ERC20Transfer memory erc20Transfer = task.budget[0];
        uint256 alreadyReserved;
        for (uint8 i; i < uint8(_reward.length); ) {
            // erc20Transfer.amount -= _reward[i].amount (underflow error, but that is not a nice custom once)
            unchecked {
                alreadyReserved += _reward[i].amount;
            }
            if (alreadyReserved > erc20Transfer.amount) {
                revert RewardAboveBudget();
            }

            application.reward[i] = _reward[i];

            if (_reward[i].nextToken) {
                alreadyReserved = 0;
                unchecked {
                    erc20Transfer = task.budget[++j];
                }
            }

            unchecked {
                ++i;
            }
        }
    }

    function _payoutTask(Task storage task) internal {
        Application storage executor = task.applications[
            task.executorApplication
        ];
        address creator = task.creator;
        Escrow escrow = task.escrow;

        uint8 j;
        uint8 rewardCount = executor.rewardCount;
        uint8 budgetCount = task.budgetCount;
        for (uint8 i; i < budgetCount; ) {
            ERC20Transfer memory erc20Transfer = task.budget[i];
            while (j < rewardCount) {
                Reward memory reward = executor.reward[j];
                escrow.transfer(
                    erc20Transfer.tokenContract,
                    reward.to,
                    reward.amount
                );
                unchecked {
                    erc20Transfer.amount -= reward.amount;
                    ++j;
                }

                if (reward.nextToken) {
                    break;
                }
            }

            // Gas optimization
            if (erc20Transfer.amount != 0) {
                escrow.transfer(
                    erc20Transfer.tokenContract,
                    creator,
                    erc20Transfer.amount
                );
            }

            unchecked {
                ++i;
            }
        }

        task.state = TaskState.Closed;
    }

    function _refundCreator(Task storage task) internal {
        uint8 budgetCount = task.budgetCount;
        address creator = task.creator;
        Escrow escrow = task.escrow;
        for (uint8 i; i < budgetCount; ) {
            ERC20Transfer memory erc20Transfer = task.budget[i];
            escrow.transfer(
                erc20Transfer.tokenContract,
                creator,
                erc20Transfer.amount
            );

            unchecked {
                ++i;
            }
        }

        task.state = TaskState.Closed;
    }
}