// File: src/GuardedLaunch/Glow.GuardedLaunch.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IGlow} from "@/interfaces/IGlow.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {UniswapV2Library} from "@/libraries/UniswapV2Library.sol";
import {GCC} from "@/GCC.sol";
import {MinerPoolAndGCA} from "@/MinerPoolAndGCA/MinerPoolAndGCA.sol";
import {Glow} from "@/GLOW.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IVetoCouncil} from "@/interfaces/IVetoCouncil.sol";
/**
 * @dev helper for managing tail and head in a mapping
 * @param tail the tail of the mapping
 * @param head the head of the mapping
 * @dev the head is the last index with data. If we need to push, we push at head + 1
 * @dev there are edge cases where head == tail and there is data,
 *         -   and conversely, head == tail and there is no data
 *         - These special cases are handled in the code
 */

struct Pointers {
    uint128 tail;
    uint128 head;
}

/**
 * @title GlowGuardedLaunch
 * @notice This contract is used to guard the launch of the GLOW token
 *               - GLOW Protocol's guarded launch is meant to protect the protocol from
 *                 malicious actors and to give the community time to audit the code
 *               - During the guarded launch, transfers are restricted to EOA's and allowlisted contracts
 *               - The veto council also has the ability to permanently freeze transfers in case of an emergency
 *                  - Post guarded-launch, Guarded Launch tokens will be airdropped 1:1 to GLOW holders
 */
contract GlowGuardedLaunch is Glow, Ownable {
    error ErrIsContract();
    error ErrNotVetoCouncilMember();
    error ErrPermanentlyFrozen();

    /* -------------------------------------------------------------------------- */
    /*                                  immutables                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice The address of the USDG contract
     */
    address public immutable USDG;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice true if transfers are permanently frozen
     */
    bool public permanentlyFreezeTransfers;

    /**
     * @notice The address of the GlowUnlocker contract
     * @dev this contract unlocks 90 million pre-minted glow tokens over 6 years
     */
    address public glowUnlocker;

    /**
     * @notice address -> isAllowListedContract
     */
    mapping(address => bool) public allowlistedContracts;

    /* -------------------------------------------------------------------------- */
    /*                                   events                                   */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Emitted when the contract is permanently frozen
     */
    event PermanentFreeze();
    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */

    /*
    * @notice Sets the immutable variables (GENESIS_TIMESTAMP, EARLY_LIQUIDITY_ADDRESS)
    * @notice sends 12 million GLW to the Early Liquidity Contract and 90 million GLW to the unlocker contract
    * @param _earlyLiquidityAddress The address of the Early Liquidity Contract
    * @param _vestingContract The address of the vesting contract
                              -unused in guarded launch
      * @param _gcaAndMinerPoolAddress The address of the GCA and Miner Pool
    * @param _vetoCouncilAddress The address of the Veto Council
    * @param _grantsTreasuryAddress The address of the Grants Treasury
    * @param _owner The address of the owner
    * @param _usdg The address of the USDG contract
    * @param _uniswapV2Factory The address of the Uniswap V2 Factory
    * @param _gccContract The address of the GCC contract
    */
    constructor(
        address _earlyLiquidityAddress,
        address _vestingContract,
        address _gcaAndMinerPoolAddress,
        address _vetoCouncilAddress,
        address _grantsTreasuryAddress,
        address _owner,
        address _usdg,
        address _uniswapV2Factory,
        address _gccContract
    )
        payable
        Glow(_earlyLiquidityAddress, _vestingContract, _gcaAndMinerPoolAddress, _vetoCouncilAddress, _grantsTreasuryAddress)
        Ownable(_owner)
    {
        allowlistedContracts[address(this)] = true;
        allowlistedContracts[_earlyLiquidityAddress] = true;
        allowlistedContracts[getPair(_uniswapV2Factory, address(this), _usdg)] = true;

        //The addresses are set as immutables in the child Glow.sol contract
        allowlistedContracts[_gcaAndMinerPoolAddress] = true;
        allowlistedContracts[_vetoCouncilAddress] = true;
        allowlistedContracts[_grantsTreasuryAddress] = true;

        address carbonCreditAuction = address(GCC(_gccContract).CARBON_CREDIT_AUCTION());
        require(carbonCreditAuction != address(0), "Glow: carbonCreditAuction is zero");
        allowlistedContracts[carbonCreditAuction] = true;
    }

    /* -------------------------------------------------------------------------- */
    /*                                  veto council                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Freezes transfers permanently
     * @dev only veto council members can call this function
     * @dev after this function is called, all transfers are permanently frozen
     */
    function freezeContract() external {
        if (!IVetoCouncil(VETO_COUNCIL_ADDRESS).isCouncilMember(msg.sender)) {
            revert ErrNotVetoCouncilMember();
        }
        permanentlyFreezeTransfers = true;
        emit PermanentFreeze();
    }

    /* -------------------------------------------------------------------------- */
    /*                                 erc20 override                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @dev override transfers to make sure that only EOA's and allowlisted contracts can send or receive USDG
     * @param from the address to send USDG from
     * @param to the address to send USDG to
     * @param value the amount of USDG to send
     */
    function _update(address from, address to, uint256 value) internal override(ERC20) {
        if (permanentlyFreezeTransfers) {
            revert ErrPermanentlyFrozen();
        }
        if (!_isZeroAddress(from)) {
            _revertIfNotAllowlistedContract(from);
            _revertIfNotAllowlistedContract(to);
        }
        super._update(from, to, value);
    }

    /* -------------------------------------------------------------------------- */
    /*                                  glow overrides                            */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc Glow
     * @dev Guarded launch does not mint tokens to the vesting contract
     */
    function _handleConstructorMint(
        address _earlyLiquidityAddress,
        address _vestingContract,
        address _grantsTreasryAddress
    ) internal override {
        _mint(_earlyLiquidityAddress, 12_000_000 ether);
        _mint(_grantsTreasryAddress, 6_000_000 ether);
    }

    /* -------------------------------------------------------------------------- */
    /*                                  utils                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev reverts if the address is a contract and not allowlisted
     */
    function _revertIfNotAllowlistedContract(address _address) internal view {
        if (_isContract(_address)) {
            if (!allowlistedContracts[_address]) {
                revert ErrIsContract();
            }
        }
    }

    /**
     * @dev returns true if the address is a contract
     * @param _address the address to check
     * @return isContract - true if the address is a contract
     */
    function _isContract(address _address) internal view returns (bool isContract) {
        assembly {
            isContract := gt(extcodesize(_address), 0)
        }
    }

    /**
     * @notice Returns the univ2 pair for a given factory and token
     * @param factory the univ2 factory
     * @param _tokenA the first token
     * @param _tokenB the second token
     * @return pair - the univ2 pair
     */
    function getPair(address factory, address _tokenA, address _tokenB) internal view virtual returns (address pair) {
        pair = UniswapV2Library.pairFor(factory, _tokenA, _tokenB);
    }
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol



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

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `requestedDecrease`.
     *
     * NOTE: Although this function is designed to avoid double spending with {approval},
     * it can still be frontrunned, preventing any attempt of allowance reduction.
     */
    function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < requestedDecrease) {
            revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
        }
        unchecked {
            _approve(owner, spender, currentAllowance - requestedDecrease);
        }

        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is
     * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, by transferring it to address(0).
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 value) internal virtual {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Alternative version of {_approve} with an optional flag that can enable or disable the Approval event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true
     * using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

// File: src/interfaces/IGlow.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IGlow is IERC20 {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error UnstakeAmountExceedsStakedBalance();
    error InsufficientClaimableBalance();
    error CannotStakeZeroTokens();
    error CannotUnstakeZeroTokens();
    error AddressAlreadySet();
    error AddressNotSet();
    error CallerNotGCA();
    error CallerNotVetoCouncil();
    error CallerNotGrantsTreasury();
    error UnstakingOnEmergencyCooldown();
    error ZeroAddressNotAllowed();
    error DuplicateAddressNotAllowed();
    error CannotClaimZeroTokens();

    /* -------------------------------------------------------------------------- */
    /*                                      events                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Emitted when a user stakes GLOW
     * @param user The address of the user that is staking
     * @param amount The amount staked
     */
    event Stake(address indexed user, uint256 amount);

    /**
     * @notice Emitted when a user unstakes GLOW
     * @param user The address of the user that is unstaking
     * @param amount The amount unstaked
     */
    event Unstake(address indexed user, uint256 amount);

    /**
     * @notice Emitted when a user claims GLOW from there unstaked positions
     * @param user The address of the user that is claiming
     * @param amount The amount claimed
     */
    event ClaimUnstakedGLW(address indexed user, uint256 amount);

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice represents an unstaked position
     * @param amount The amount of GLOW unstaked
     * @param cooldownEnd The timestamp when the user can reclaim the tokens
     */
    struct UnstakedPosition {
        uint192 amount;
        uint64 cooldownEnd;
    }

    /**
     * @dev helper for managing tail and head in a mapping
     * @param tail the tail of the mapping
     * @param head the head of the mapping
     * @dev the head is the last index with data. If we need to push, we push at head + 1
     * @dev there are edge cases where head == tail and there is data,
     *         -   and conversely, head == tail and there is no data
     *         - These special cases are handled in the code
     */
    struct Pointers {
        uint128 tail;
        uint128 head;
    }

    /* -------------------------------------------------------------------------- */
    /*                                   staking                                  */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice The entry point for a user to stake glow.
     * @notice A user earns 1 ratify/reject vote per glw staked
     * @param amount The amount of GLOW to stake
     */
    function stake(uint256 amount) external;

    /**
     * @notice The entry point for a user to unstake glow.
     * @param amount The amount of GLOW to unstake
     */
    function unstake(uint256 amount) external;

    /* -------------------------------------------------------------------------- */
    /*                                   inflation                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Entry point for users to claim unstaked tokens that are no longer on cooldown
     * @param amount The amount of tokens to claim
     * @dev emits a ```ClaimUnstakedGLW``` event
     */
    function claimUnstakedTokens(uint256 amount) external;

    /**
     * @notice Allows the GCA and Miner Pool Contract to claim GLW from inflation
     * @notice The GCA and Miner Pool Contract receives 185,00 * 1e18 tokens per week
     */
    function claimGLWFromGCAAndMinerPool() external returns (uint256);

    /**
     * @notice Allows the Veto Council to claim GLW from inflation
     * @notice The veto council receives 5,000 * 1e18 tokens per week
     */
    function claimGLWFromVetoCouncil() external returns (uint256);

    /**
     * @notice Allows the Grants Treasury to claim GLW from inflation
     * @notice The grants treasury receives 40,000 * 1e18 tokens per week
     */
    function claimGLWFromGrantsTreasury() external returns (uint256);

    /* -------------------------------------------------------------------------- */
    /*                            view unstaked positions                          */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Returns the unstaked positions of a user
     * @param account The address of the user
     */
    function unstakedPositionsOf(address account) external view returns (UnstakedPosition[] memory);

    /**
     * @notice Returns the unstaked positions of a user
     * @param account The address of the user
     * @param start The start index of the positions to return
     * @param end The end index of the positions to return
     */
    function unstakedPositionsOf(address account, uint256 start, uint256 end)
        external
        view
        returns (UnstakedPosition[] memory);

    /* -------------------------------------------------------------------------- */
    /*                             view inflation data                           */
    /* -------------------------------------------------------------------------- */
    /**
     * @return lastClaimTimestamp The last time the GCA and Miner Pool Contract claimed GLW
     * @return totalAlreadyClaimed The total amount of GLW already claimed by the GCA and Miner Pool Contract
     * @return totalToClaim The total amount of GLW available to claim by the GCA and Miner Pool Contract
     */
    function gcaInflationData()
        external
        view
        returns (uint256 lastClaimTimestamp, uint256 totalAlreadyClaimed, uint256 totalToClaim);

    /**
     * @return lastClaimTimestamp The last time the Veto Council claimed GLW
     * @return totalAlreadyClaimed The total amount of GLW already claimed by the Veto Council
     * @return totalToClaim The total amount of GLW available to claim by the Veto Council
     */
    function vetoCouncilInflationData()
        external
        view
        returns (uint256 lastClaimTimestamp, uint256 totalAlreadyClaimed, uint256 totalToClaim);

    /**
     * @return lastClaimTimestamp The last time the Grants Treasury claimed GLW
     * @return totalAlreadyClaimed The total amount of GLW already claimed by the Grants Treasury
     * @return totalToClaim The total amount of GLW available to claim by the Grants Treasury
     */
    function grantsTreasuryInflationData()
        external
        view
        returns (uint256 lastClaimTimestamp, uint256 totalAlreadyClaimed, uint256 totalToClaim);

    /* -------------------------------------------------------------------------- */
    /*                                   view                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @return the genesis timestamp
     */
    function GENESIS_TIMESTAMP() external view returns (uint256);

    /**
     * @notice the total amount of GLW currently staked by {account}
     * @return numStaked total amount of GLW currently staked by {account}
     * @param account the address of the account to get the staked balance of
     */
    function numStaked(address account) external view returns (uint256);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol



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

pragma solidity ^0.8.20;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    // solhint-disable-next-line var-name-mixedcase
    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @dev See {IERC20Permit-permit}.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /**
     * @dev See {IERC20Permit-nonces}.
     */
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /**
     * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }
}

// File: src/libraries/UniswapV2Library.sol



pragma solidity ^0.8.19;

import {IUniswapV2Pair} from "@/interfaces/IUniswapV2Pair.sol";

library UniswapV2Library {
    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS");
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(abi.encodePacked(token0, token1)),
                            hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash
                        )
                    )
                )
            )
        );
    }

    // fetches and sorts the reserves for a pair
    function getReserves(address factory, address tokenA, address tokenB)
        internal
        view
        returns (uint256 reserveA, uint256 reserveB)
    {
        (address token0,) = sortTokens(tokenA, tokenB);
        (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) internal pure returns (uint256 amountB) {
        require(amountA > 0, "UniswapV2Library: INSUFFICIENT_AMOUNT");
        require(reserveA > 0 && reserveB > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
        amountB = amountA * (reserveB) / reserveA;
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
        internal
        pure
        returns (uint256 amountOut)
    {
        require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
        uint256 amountInWithFee = amountIn * (997);
        uint256 numerator = amountInWithFee * (reserveOut);
        uint256 denominator = reserveIn * (1000) + (amountInWithFee);
        amountOut = numerator / denominator;
    }

    // given an output amount of an asset and pair reserx   ves, returns a required input amount of the other asset
    function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut)
        internal
        pure
        returns (uint256 amountIn)
    {
        require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT");
        require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY");
        uint256 numerator = reserveIn * (amountOut) * (1000);
        uint256 denominator = reserveOut - (amountOut) * (997);
        amountIn = (numerator / denominator) + (1);
    }

    // // performs chained getAmountOut calculations on any number of pairs
    // function getAmountsOut(address factory, uint256 amountIn, address[] memory path)
    //     internal
    //     view
    //     returns (uint256[] memory amounts)
    // {
    //     require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
    //     amounts = new uint[](path.length);
    //     amounts[0] = amountIn;
    //     for (uint256 i; i < path.length - 1; i++) {
    //         (uint256 reserveIn, uint256 reserveOut) = getReserves(factory, path[i], path[i + 1]);
    //         amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
    //     }
    // }

    // // performs chained getAmountIn calculations on any number of pairs
    // function getAmountsIn(address factory, uint256 amountOut, address[] memory path)
    //     internal
    //     view
    //     returns (uint256[] memory amounts)
    // {
    //     require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
    //     amounts = new uint[](path.length);
    //     amounts[amounts.length - 1] = amountOut;
    //     for (uint256 i = path.length - 1; i > 0; i--) {
    //         (uint256 reserveIn, uint256 reserveOut) = getReserves(factory, path[i - 1], path[i]);
    //         amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
    //     }
    // }
}

// File: src/GCC.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IGCC} from "@/interfaces/IGCC.sol";
import {ICarbonCreditAuction} from "@/interfaces/ICarbonCreditAuction.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {IGovernance} from "@/interfaces/IGovernance.sol";
import {CarbonCreditDescendingPriceAuction} from "@/CarbonCreditDescendingPriceAuction.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IUniswapRouterV2} from "@/interfaces/IUniswapRouterV2.sol";
import {ImpactCatalyst} from "@/ImpactCatalyst.sol";
import {IERC20Permit} from "@/interfaces/IERC20Permit.sol";
import {UniswapV2Library} from "@/libraries/UniswapV2Library.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

/**
 * @title GCC (Glow Carbon Credit)
 * @author DavidVorick
 * @author 0xSimon(twitter) - 0xSimbo(github)
 * @notice This contract is the ERC20 token for Glow Carbon Credits (GCC).
 *         - 1 GCC or (1e18 wei of GCC) represents 1 metric ton of CO2 offsets
 *         - GCC is minted by the Glow protocol as farms produce clean solar
 *         - GCC can be committed for nominations and permanent impact power
 *         - Nominations are used to vote on proposals in governance and are in 12 decimals
 *         - Impact power is an on-chain record of the sum of total impact power earned by a user
 *         - It currently has no use, but can be used to integrate with other protocols
 *         - Once GCC is committed, it can't be uncommitted
 *         - GCC is sold in the carbon credit auction
 *          - The amount of nominations earned is equal to the sqrt(amountGCCAddedToUniV2LP * amountUSDCAddedToUniV2LP)
 *              - earned from a swap in the commitGCC or commitUSDC functions in the `impactCatalyst`
 *              - When committing USDC, the amount of nominations earned is equal to the amount of USDC committed
 */

contract GCC is ERC20, ERC20Burnable, IGCC, EIP712 {
    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /// @notice The EIP712 typehash for the CommitPermit struct used by the permit
    bytes32 public constant COMMIT_PERMIT_TYPEHASH = keccak256(
        "CommitPermit(address owner,address spender,address rewardAddress,address referralAddress,uint256 amount,uint256 nonce,uint256 deadline)"
    );

    /// @notice The maximum shift for a bucketId
    uint256 private constant _BITS_IN_UINT = 256;

    /* -------------------------------------------------------------------------- */
    /*                                  immutables                                */
    /* -------------------------------------------------------------------------- */
    /// @notice The address of the CarbonCreditAuction contract
    ICarbonCreditAuction public immutable CARBON_CREDIT_AUCTION;

    /// @notice The address of the GCAAndMinerPool contract
    address public immutable GCA_AND_MINER_POOL_CONTRACT;

    /// @notice the address of the governance contract
    IGovernance public immutable GOVERNANCE;

    /// @notice the address of the GLOW token
    address public immutable GLOW;

    /// @notice the address of the ImpactCatalyst contract
    /// @dev the impact catalyst is responsible for handling the commitments of GCC and USDC
    ImpactCatalyst public immutable IMPACT_CATALYST;

    /// @notice The Uniswap router
    /// @dev used to swap USDC for GCC and vice versa
    IUniswapRouterV2 public immutable UNISWAP_ROUTER;

    /// @notice The address of the USDC token
    address public immutable USDC;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice The bitmap of minted buckets
     * @dev key 0 contains the first 256 buckets, key 1 contains the next 256 buckets, etc.
     */
    mapping(uint256 => uint256) private _mintedBucketsBitmap;

    /**
     * @notice The total impact power earned by a user from their USDC or GCC commitments
     */
    mapping(address => uint256) public totalImpactPowerEarned;

    /**
     * @notice The allowances for committing GCC
     * @dev similar to ERC20
     */
    mapping(address => mapping(address => uint256)) private _commitGCCAllowances;

    /**
     * @notice The next commit nonce for a user
     */
    mapping(address => uint256) public nextCommitNonce;

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice GCC constructor
     * @param _gcaAndMinerPoolContract The address of the GCAAndMinerPool contract
     * @param _governance The address of the governance contract
     * @param _glowToken The address of the GLOW token
     * @param _usdc The address of the USDC token
     * @param _uniswapRouter The address of the Uniswap V2 router
     */
    constructor(
        address _gcaAndMinerPoolContract,
        address _governance,
        address _glowToken,
        address _usdc,
        address _uniswapRouter
    ) payable ERC20("Glow Carbon Certificate", "GCC-BETA") EIP712("Glow Carbon Certificate", "1") {
        // Set the immutable variables
        USDC = _usdc;
        GCA_AND_MINER_POOL_CONTRACT = _gcaAndMinerPoolContract;
        UNISWAP_ROUTER = IUniswapRouterV2(_uniswapRouter);
        GOVERNANCE = IGovernance(_governance);
        GLOW = _glowToken;
        //Create the carbon credit auction directly in the constructor
        CarbonCreditDescendingPriceAuction cccAuction = new CarbonCreditDescendingPriceAuction({
            glow: IERC20(_glowToken),
            gcc: IERC20(address(this)),
            startingPrice: 1e5 // Carbon Credit Auction sells increments of 1e6 GCC,
                // Setting the price to 1e5 per unit means that 1 GCC = .1 GLOW
        });

        CARBON_CREDIT_AUCTION = ICarbonCreditAuction(address(cccAuction));
        //Create the impact catalyst
        address factory = UNISWAP_ROUTER.factory();
        address pair = getPair(factory, _usdc);
        //Mint 1 to set the LP with USDC
        //Note: On Guarded Launch the LP is set with USDG
        if (block.chainid == 1) {
            _mint(tx.origin, 1.1 ether);
        }
        //The impact catalyst is responsible for handling the commitments of GCC and USDC
        IMPACT_CATALYST = new ImpactCatalyst(_usdc, _uniswapRouter, factory, pair);
    }

    /* -------------------------------------------------------------------------- */
    /*                                   minting                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IGCC
     */
    function mintToCarbonCreditAuction(uint256 bucketId, uint256 amount) external {
        if (msg.sender != GCA_AND_MINER_POOL_CONTRACT) _revert(IGCC.CallerNotGCAContract.selector);
        _setBucketMinted(bucketId);
        if (amount > 0) {
            CARBON_CREDIT_AUCTION.receiveGCC(amount);
            _mint(address(CARBON_CREDIT_AUCTION), amount);
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                                   commits                                  */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc IGCC
     */
    function commitGCC(uint256 amount, address rewardAddress, address referralAddress, uint256 minImpactPower)
        public
        returns (uint256 usdcEffect, uint256 impactPower)
    {
        //Transfer GCC from the msg.sender to the impact catalyst
        _transfer(msg.sender, address(IMPACT_CATALYST), amount);
        //get back the amount of USDC that was used in the LP and the impact power earned
        (usdcEffect, impactPower) = IMPACT_CATALYST.commitGCC(amount, minImpactPower);
        //handle the commitment
        _handleCommitment(msg.sender, rewardAddress, amount, usdcEffect, impactPower, referralAddress);
    }

    /**
     * @inheritdoc IGCC
     */
    function commitGCC(uint256 amount, address rewardAddress, uint256 minImpactPower)
        external
        returns (uint256, uint256)
    {
        //Same as above, but with no referrer
        return (commitGCC(amount, rewardAddress, address(0), minImpactPower));
    }

    /**
     * @inheritdoc IGCC
     */
    function commitGCCFor(
        address from,
        address rewardAddress,
        uint256 amount,
        address referralAddress,
        uint256 minImpactPower
    ) public returns (uint256 usdcEffect, uint256 impactPower) {
        //Transfer GCC `from` to the impact catalyst
        transferFrom(from, address(IMPACT_CATALYST), amount);
        //If the msg.sender is not `from`, then check and decrease the allowance
        if (msg.sender != from) {
            _decreaseCommitAllowance(from, msg.sender, amount, false);
        }
        //get back the amount of USDC that was used in the LP and the impact power earned
        (usdcEffect, impactPower) = IMPACT_CATALYST.commitGCC(amount, minImpactPower);
        //handle the commitment
        _handleCommitment(from, rewardAddress, amount, usdcEffect, impactPower, referralAddress);
    }

    /**
     * @inheritdoc IGCC
     */
    function commitGCCFor(address from, address rewardAddress, uint256 amount, uint256 minImpactPower)
        public
        returns (uint256, uint256)
    {
        //Same as above, but with no referrer
        return (commitGCCFor(from, rewardAddress, amount, address(0), minImpactPower));
    }

    /**
     * @inheritdoc IGCC
     */
    function commitGCCForAuthorized(
        address from,
        address rewardAddress,
        uint256 amount,
        uint256 deadline,
        bytes calldata signature,
        address referralAddress,
        uint256 minImpactPower
    ) public returns (uint256, uint256) {
        //Check the deadline
        if (block.timestamp > deadline) {
            _revert(IGCC.CommitPermitSignatureExpired.selector);
        }

        //Load the next nonce
        uint256 _nextCommitNonce = nextCommitNonce[from]++;
        //Construct the message to be signed
        bytes32 message = _constructCommitPermitDigest(
            from, msg.sender, rewardAddress, referralAddress, amount, _nextCommitNonce, deadline
        );
        //Check the signature
        if (!_checkCommitPermitSignature(from, message, signature)) {
            _revert(IGCC.CommitSignatureInvalid.selector);
        }
        //Increase the allowance for the msg.sender on the `from` account
        _increaseCommitAllowance(from, msg.sender, amount, false);
        uint256 transferAllowance = allowance(from, msg.sender);
        if (transferAllowance < amount) {
            _approve(from, msg.sender, amount, false);
        }
        //Commit the GCC
        return (commitGCCFor(from, rewardAddress, amount, referralAddress, minImpactPower));
    }

    /**
     * @inheritdoc IGCC
     */
    function commitGCCForAuthorized(
        address from,
        address rewardAddress,
        uint256 amount,
        uint256 deadline,
        bytes calldata signature,
        uint256 minImpactPower
    ) external returns (uint256 usdcEffect, uint256 impactPower) {
        //Same as above, but with no referrer
        return (commitGCCForAuthorized(from, rewardAddress, amount, deadline, signature, address(0), minImpactPower));
    }

    /**
     * @inheritdoc IGCC
     */
    function commitUSDC(uint256 amount, address rewardAddress, address referralAddress, uint256 minImpactPower)
        public
        returns (uint256 impactPower)
    {
        //Read in the balance of the impact catalyst before the transfer
        uint256 impactCatalystBalBefore = IERC20(USDC).balanceOf(address(IMPACT_CATALYST));
        //Transfer USDC from the msg.sender to the impact catalyst
        IERC20(USDC).transferFrom(msg.sender, address(IMPACT_CATALYST), amount);
        //Read in the balance of the impact catalyst after the transfer
        uint256 impactCatalystBalAfter = IERC20(USDC).balanceOf(address(IMPACT_CATALYST));
        //Calculate the actual amount of USDC available from the transfer (in case of fees since USDC is upgradable)
        uint256 usdcUsing = impactCatalystBalAfter - impactCatalystBalBefore;
        //get back the impaoct power earned
        impactPower = IMPACT_CATALYST.commitUSDC(usdcUsing, minImpactPower);
        //handle the commitment
        _handleUSDCcommitment(msg.sender, rewardAddress, amount, impactPower, referralAddress);
    }

    /**
     * @inheritdoc IGCC
     */
    function commitUSDC(uint256 amount, address rewardAddress, uint256 minImpactPower) external returns (uint256) {
        //Same as above, but with no referrer
        return (commitUSDC(amount, rewardAddress, address(0), minImpactPower));
    }

    /**
     * @inheritdoc IGCC
     */
    function commitUSDCSignature(
        uint256 amount,
        address rewardAddress,
        address referralAddress,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint256 minImpactPower
    ) external returns (uint256 impactPower) {
        // Execute the transfer with a signed authorization
        IERC20Permit paymentToken = IERC20Permit(USDC);
        uint256 allowance = paymentToken.allowance(msg.sender, address(this));
        //Check allowance to avoid front-running issues
        if (allowance < amount) {
            paymentToken.permit(msg.sender, address(this), amount, deadline, v, r, s);
        }
        return (commitUSDC(amount, rewardAddress, referralAddress, minImpactPower));
    }

    /* -------------------------------------------------------------------------- */
    /*                        commit allowance  & allowances                      */
    /* -------------------------------------------------------------------------- */
    /// @inheritdoc IGCC
    function setAllowances(address spender, uint256 transferAllowance, uint256 committingAllowance) external {
        _approve(msg.sender, spender, transferAllowance);
        _commitGCCAllowances[msg.sender][spender] = committingAllowance;
        emit IGCC.CommitGCCAllowance(msg.sender, spender, committingAllowance);
    }

    /// @inheritdoc IGCC
    function increaseAllowances(address spender, uint256 addedValue) public {
        _approve(msg.sender, spender, allowance(msg.sender, spender) + addedValue);
        _increaseCommitAllowance(msg.sender, spender, addedValue, true);
    }

    /// @inheritdoc IGCC
    function decreaseAllowances(address spender, uint256 requestedDecrease) public {
        uint256 currentAllowance = allowance(msg.sender, spender);
        if (currentAllowance < requestedDecrease) {
            revert ERC20.ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
        }
        unchecked {
            _approve(msg.sender, spender, currentAllowance - requestedDecrease);
        }
        _decreaseCommitAllowance(msg.sender, spender, requestedDecrease, true);
    }

    /**
     * @inheritdoc IGCC
     */
    function increaseCommitAllowance(address spender, uint256 amount) external override {
        _increaseCommitAllowance(msg.sender, spender, amount, true);
    }

    /**
     * @inheritdoc IGCC
     */
    function decreaseCommitAllowance(address spender, uint256 amount) external override {
        _decreaseCommitAllowance(msg.sender, spender, amount, true);
    }

    /* -------------------------------------------------------------------------- */
    /*                              view functions                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc IGCC
     */
    function commitAllowance(address account, address spender) public view override returns (uint256) {
        return _commitGCCAllowances[account][spender];
    }

    /**
     * @inheritdoc IGCC
     */
    function isBucketMinted(uint256 bucketId) external view returns (bool) {
        (uint256 key, uint256 shift) = _getKeyAndShiftFromBucketId(bucketId);
        return _mintedBucketsBitmap[key] & (1 << shift) != 0;
    }

    /**
     * @notice Returns the domain separator used in the permit signature
     * @dev Should be deterministic
     * @return result The domain separator
     */
    function domainSeparatorV4() public view returns (bytes32) {
        return _domainSeparatorV4();
    }

    /* -------------------------------------------------------------------------- */
    /*                              private functions                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice sets the bucket as minted
     * @param bucketId the id of the bucket to set as minted
     * @dev reverts if the bucket has already been minted
     */
    function _setBucketMinted(uint256 bucketId) private {
        (uint256 key, uint256 shift) = _getKeyAndShiftFromBucketId(bucketId);
        //Can't overflow because _BITS_IN_UINT is 256
        uint256 bitmap = _mintedBucketsBitmap[key];
        if (bitmap & (1 << shift) != 0) _revert(IGCC.BucketAlreadyMinted.selector);
        _mintedBucketsBitmap[key] = bitmap | (1 << shift);
    }

    /**
     * @notice handles the storage writes and event emissions relating to committing gcc.
     * @param from the address of the account committing the credits
     * @param rewardAddress the address to receive the benefits of committing
     * @param usdcEffect - the amount of USDC added into the uniswap v2 lp position
     * @param gccCommitted the amount of GCC committed
     * @param impactPower the effect of committing on the USDC balance
     * @param referralAddress the address of the referrer (zero for no referrer)
     */
    function _handleCommitment(
        address from,
        address rewardAddress,
        uint256 gccCommitted,
        uint256 usdcEffect,
        uint256 impactPower,
        address referralAddress
    ) private {
        if (from == referralAddress) _revert(IGCC.CannotReferSelf.selector);
        //committing USDC calls syncProposals in governance to ensure that the proposals are up to date
        //This design is meant to ensure that the proposals are as up to date as possible
        GOVERNANCE.syncProposals();
        //Increase the total impact power earned by the reward address
        totalImpactPowerEarned[rewardAddress] += impactPower;
        //Grant the nominations to the reward address
        GOVERNANCE.grantNominations(rewardAddress, impactPower);
        //Emit a GCCCommitted event
        emit IGCC.GCCCommitted(from, rewardAddress, gccCommitted, usdcEffect, impactPower, referralAddress);
    }

    /**
     * @notice handles the storage writes and event emissions relating to committing USDC
     * @dev should only be used internally and by function that require a transfer of {amount} to address(this)
     * @param from the address of the account committing the credits
     * @param rewardAddress the address to receive the benefits of committing
     * @param amount the amount of USDC TO commit
     * @param referralAddress the address of the referrer (zero for no referrer)
     */
    function _handleUSDCcommitment(
        address from,
        address rewardAddress,
        uint256 amount,
        uint256 impactPower,
        address referralAddress
    ) private {
        if (from == referralAddress) _revert(IGCC.CannotReferSelf.selector);
        //committing USDC calls syncProposals in governance to ensure that the proposals are up to date
        //This design is meant to ensure that the proposals are as up to date as possible
        GOVERNANCE.syncProposals();
        //Increase the total impact power earned by the reward address
        totalImpactPowerEarned[rewardAddress] += impactPower;
        //Grant the nominations to the reward address
        GOVERNANCE.grantNominations(rewardAddress, impactPower);
        //Emit a USDCCommitted event
        emit IGCC.USDCCommitted(from, rewardAddress, amount, impactPower, referralAddress);
    }

    /**
     * @dev internal function to increase the committing allowance
     * @param from the address of the account to increase the allowance from
     * @param spender the address of the spender to increase the allowance for
     * @param amount the amount to increase the allowance by
     * @param emitEvent whether or not to emit the event
     */
    function _increaseCommitAllowance(address from, address spender, uint256 amount, bool emitEvent) private {
        if (amount == 0) {
            _revert(IGCC.MustIncreaseCommitAllowanceByAtLeastOne.selector);
        }
        uint256 currentAllowance = _commitGCCAllowances[from][spender];
        uint256 newAllowance;
        unchecked {
            newAllowance = currentAllowance + amount;
        }
        //If there was an overflow, then we set the new allowance to type(uint).max
        //Since that is where the allowance will be capped anyway
        if (newAllowance <= currentAllowance) {
            newAllowance = type(uint256).max;
        }
        _commitGCCAllowances[from][spender] = newAllowance;
        if (emitEvent) {
            emit IGCC.CommitGCCAllowance(from, spender, newAllowance);
        }
    }

    /**
     * @dev internal function to decrease the committing allowance
     * @param from the address of the account to decrease the allowance from
     * @param spender the address of the spender to decrease the allowance for
     * @param amount the amount to decrease the allowance by
     * @param emitEvent whether or not to emit the event
     * @dev underflow auto-reverts due to built in safemath
     */
    function _decreaseCommitAllowance(address from, address spender, uint256 amount, bool emitEvent) private {
        uint256 currentAllowance = _commitGCCAllowances[from][spender];

        uint256 newAllowance = currentAllowance - amount;
        _commitGCCAllowances[from][spender] = newAllowance;
        if (emitEvent) {
            emit IGCC.CommitGCCAllowance(from, spender, newAllowance);
        }
    }

    //-------------  PRIVATE UTILS  --------------------//
    /**
     * @notice Returns the key and shift for a bucketId
     * @return key The key for the bucketId
     * @return shift The shift for the bucketId
     * @dev cant overflow because _BITS_IN_UINT is 256
     * @dev no division by zero because _BITS_IN_UINT is 256
     */
    function _getKeyAndShiftFromBucketId(uint256 bucketId) private pure returns (uint256 key, uint256 shift) {
        key = bucketId / _BITS_IN_UINT;
        shift = bucketId % _BITS_IN_UINT;
    }

    /**
     * @dev Constructs a committing permit EIP712 message hash to be signed
     * @param owner The owner of the funds
     * @param spender The spender
     * @param rewardAddress - the address to receive the benefits of committing
     * @param referralAddress - the address of the referrer
     * @param amount The amount of funds
     * @param nonce The next nonce
     * @param deadline The deadline for the signature to be valid
     * @return digest The EIP712 digest
     */
    function _constructCommitPermitDigest(
        address owner,
        address spender,
        address rewardAddress,
        address referralAddress,
        uint256 amount,
        uint256 nonce,
        uint256 deadline
    ) private view returns (bytes32) {
        return _hashTypedDataV4(
            keccak256(
                abi.encode(
                    COMMIT_PERMIT_TYPEHASH, owner, spender, rewardAddress, referralAddress, amount, nonce, deadline
                )
            )
        );
    }

    /**
     * @dev Checks if the signature provided is valid for the provided data, hash.
     * @param signer The address of the signer.
     * @param message The EIP-712 digest.
     * @param signature The signature, in bytes.
     * @return bool indicating if the signature was valid (true) or not (false).
     * @dev accounts for EIP-1271 magic values as well
     */
    function _checkCommitPermitSignature(address signer, bytes32 message, bytes memory signature)
        private
        view
        returns (bool)
    {
        return SignatureChecker.isValidSignatureNow(signer, message, signature);
    }

    /**
     * @notice Returns the univ2 pair for a given factory and token
     * @param factory The address of the univ2 factory
     * @param _usdc The address of the USDC token
     * @return pair The address of the univ2 pair of the factory and token with this contract
     */
    function getPair(address factory, address _usdc) internal view virtual returns (address) {
        return UniswapV2Library.pairFor(factory, _usdc, address(this));
    }
    /**
     * @notice More efficiently reverts with a bytes4 selector
     * @param selector The selector to revert with
     */

    function _revert(bytes4 selector) internal pure {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }
}

// File: src/MinerPoolAndGCA/MinerPoolAndGCA.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {GCA} from "./GCA.sol";
import {IGCA} from "@/interfaces/IGCA.sol";
import {IVetoCouncil} from "@/interfaces/IVetoCouncil.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {IMinerPool} from "@/interfaces/IMinerPool.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {BucketSubmission} from "./BucketSubmission.sol";
import {MerkleProofLib} from "@solady/utils/MerkleProofLib.sol";
import {ISafetyDelay} from "@/SafetyDelay.sol";
import {IGCC} from "@/interfaces/IGCC.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {_BUCKET_DURATION} from "@/Constants/Constants.sol";

/**
 * @title Miner Pool And GCA
 * @author @DavidVorick
 * @author @0xSimon(twitter) - 0xSimon(github)
 *  @notice this contract allows veto council members to delay buckets as defined in the `GCA` contract
 * @notice It is the entry point for farms participating in GLOW to claim their rewards for their contributions
 */
contract MinerPoolAndGCA is GCA, EIP712, IMinerPool, BucketSubmission {
    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev the amount to increase the finalization timestamp of a bucket by
     *             -   only veto council agents can delay a bucket.
     *             -   the delay is 13 weeks
     */
    uint256 private constant _BUCKET_DELAY_DURATION = uint256(7 days) * 13;

    /// @dev a helper used in a bitmap
    uint256 private constant _BITS_IN_UINT = 256;

    /// @dev the typehash for the claim reward from bucket eip712 message
    bytes32 private constant _CLAIM_REWARD_FROM_BUCKET_TYPEHASH = keccak256(
        "ClaimRewardFromBucket(uint256 bucketId,uint256 glwWeight,uint256 usdcWeight,uint256 index,bool claimFromInflation)"
    );

    /**
     * @notice the total amount of glow rewards available for farms per bucket
     */
    uint256 public constant GLOW_REWARDS_PER_BUCKET = 175_000 ether;

    /* -------------------------------------------------------------------------- */
    /*                                  immutables                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the address of the early liquidity contract
     * @dev used for authorization in {donateToUSDCMinerRewardsPoolEarlyLiquidity}
     */
    address private immutable _EARLY_LIQUIDITY;

    /**
     * @dev the address of the veto council contract.
     */
    address private immutable _VETO_COUNCIL;

    /// @notice USDC token address
    address public immutable USDC;

    /// @notice the holding contract where intermediary rewards are stored
    /// @dev when a farm earns a USDC reward, it is sent to the holding contract
    ///     - where it will wait a minimum of 1 week before being sent to the farm
    ///     - this is in place to prevent a large amount of USDC from being sent to a farm
    ///           -   mistakenly or on purpose
    ///     - If such a case happens, the Veto Council can delay the holding contract by 13 weeks
    ///     - This should give enough time to rectify the situation
    ISafetyDelay public immutable HOLDING_CONTRACT;

    /// @notice the GCC contract
    IGCC public immutable GCC;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev a mapping of (bucketId / 256) -> user  -> bitmap
     */
    mapping(uint256 => mapping(address => uint256)) private _bucketClaimBitmap;

    /**
     * @dev a mapping of (bucketId / 256) -> bitmap
     */
    mapping(uint256 => uint256) private _mintedToCarbonCreditAuctionBitmap;

    /**
     * @dev a mapping of (bucketId / 256) -> bitmap
     * @dev a bucket can only be delayed once
     */
    mapping(uint256 => uint256) private _bucketDelayedBitmap;

    /**
     * @dev a mapping of bucketId -> pushed weights
     * - we could split this up into a packed map of pushedGlwWeight and pushedUSDCWeight
     *         and use one slot to fit 4 (uint32 pushedGlwWeight, uint32 pushedUSDCWeight) tuples,
     *         but since this slot will only be cold for the first write of each bucket claim,
     *         it's not worth the additional complexity and gas costs on each subsequent write
     *         to handle the packing and unpacking.
     */
    mapping(uint256 => PushedWeights) internal _weightsPushed;

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */

    /**
     * @param pushedGlwWeight - the aggregate amount of glw weight pushed
     * @param pushedUSDCWeight - the aggregate amount of USDC weight pushed
     * @dev meant to be used in conjunction with the _weightsPushed mapping
     *       - when a user claims from a bucket, the pushed weights are added to the total weights
     *       - these are tracked to ensure that the pushed weights don't overflow the total weights
     *       - that were put in place for that specific bucket
     */
    struct PushedWeights {
        uint64 pushedGlwWeight;
        uint64 pushedUSDCWeight;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice constructs a new MinerPoolAndGCA contract
     * @param _gcaAgents the addresses of the gca agents the contract starts with
     * @param _glowToken the address of the glow token
     * @param _governance the address of the governance contract
     * @param _requirementsHash the requirements hash of GCA Agents
     * @param _usdcToken - the USDC token address
     * @param _vetoCouncil - the address of the veto council contract.
     * @param _holdingContract - the address of the holding contract
     * @param _gcc - the address of the gcc contract
     */
    constructor(
        address[] memory _gcaAgents,
        address _glowToken,
        address _governance,
        bytes32 _requirementsHash,
        address _earlyLiquidity,
        address _usdcToken,
        address _vetoCouncil,
        address _holdingContract,
        address _gcc
    ) payable GCA(_gcaAgents, _glowToken, _governance, _requirementsHash) EIP712("GCA and MinerPool", "1") {
        _EARLY_LIQUIDITY = _earlyLiquidity;
        _VETO_COUNCIL = _vetoCouncil;
        HOLDING_CONTRACT = ISafetyDelay(_holdingContract);
        USDC = _usdcToken;
        GCC = IGCC(_gcc);
    }

    /* -------------------------------------------------------------------------- */
    /*                                   donations                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc IMinerPool
     */
    function donateToUSDCMinerRewardsPool(uint256 amount) external virtual {
        uint256 balBefore = IERC20(USDC).balanceOf(address(HOLDING_CONTRACT));
        SafeERC20.safeTransferFrom(IERC20(USDC), msg.sender, address(HOLDING_CONTRACT), amount);
        uint256 transferredBalance = IERC20(USDC).balanceOf(address(HOLDING_CONTRACT)) - balBefore;
        _addToCurrentBucket(transferredBalance);
    }

    /**
     * @inheritdoc IMinerPool
     */
    function donateToUSDCMinerRewardsPoolEarlyLiquidity(uint256 amount) external virtual {
        if (msg.sender != _EARLY_LIQUIDITY) _revert(IMinerPool.CallerNotEarlyLiquidity.selector);
        _addToCurrentBucket(amount);
    }

    /* -------------------------------------------------------------------------- */
    /*                       minting to carbon credit auction                     */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Handles minting to the carbon credit auction in case the bucket is finalized and no one has claimed from it
     * @param bucketId - the id of the bucket
     */
    function handleMintToCarbonCreditAuction(uint256 bucketId) external {
        if (!isBucketFinalized(bucketId)) {
            _revert(IMinerPool.BucketNotFinalized.selector);
        }
        uint256 globalPackedState = getPackedBucketGlobalState(bucketId);
        uint256 amountToMint = globalPackedState & _UINT128_MASK;
        _handleMintToCarbonCreditAuction(bucketId, amountToMint);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 claiming rewards                           */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IMinerPool
     */
    function claimRewardFromBucket(
        uint256 bucketId,
        uint256 glwWeight,
        uint256 usdcWeight,
        bytes32[] calldata proof,
        uint256 index,
        address user,
        bool claimFromInflation,
        bytes memory signature
    ) external {
        if (msg.sender != user) {
            bytes32 hash = createClaimRewardFromBucketDigest(bucketId, glwWeight, usdcWeight, index, claimFromInflation);
            if (!SignatureChecker.isValidSignatureNow(user, hash, signature)) {
                _revert(IMinerPool.SignatureDoesNotMatchUser.selector);
            }
        }
        if (!isBucketFinalized(bucketId)) {
            _revert(IMinerPool.BucketNotFinalized.selector);
        }
        if (claimFromInflation) {
            claimGlowFromInflation();
        }
        {
            bytes32 root = getBucketRootAtIndexEfficient(bucketId, index);
            _checkProof(user, glwWeight, usdcWeight, proof, root);
        }

        uint256 globalStatePackedData = getPackedBucketGlobalState(bucketId);

        /**
         * Bit Layout of packed global state
         *     [0-127] - totalNewGCC
         *     [128-191] - totalGLWRewardsWeight
         *     [192-255] - totalUSDCRewardsWeight
         */
        uint256 totalUSDCWeight = globalStatePackedData >> 192;
        uint256 totalGlwWeight = globalStatePackedData >> 128 & _UINT64_MASK;
        _checkWeightsForOverflow({
            bucketId: bucketId,
            totalGlwWeight: totalGlwWeight,
            totalUSDCWeight: totalUSDCWeight,
            glwWeight: glwWeight,
            usdcWeight: usdcWeight
        });

        _handleMintToCarbonCreditAuction(bucketId, globalStatePackedData & _UINT128_MASK);

        //no need to use a mask since totalUSDCWeight uses the last 64 bits, so we can just shift
        {
            uint256 userBitmap = _getUserBitmapForBucket(bucketId, user);
            userBitmap = _checkClaimAvailableAndReturnNewBitmap(bucketId, userBitmap);
            _setUserBitmapForBucket(bucketId, user, userBitmap);
        }

        //Just in case a faulty report is submitted, we need to choose the min of _glwWeight and totalGlwWeight
        // so that we don't overflow the available USDC rewards
        // and grab rewards from other buckets
        uint256 amountInBucket = _getAmountForTokenAndInitIfNot(bucketId);
        _revertIfGreater(usdcWeight, totalUSDCWeight, IMinerPool.USDCWeightGreaterThanTotalWeight.selector);
        amountInBucket = amountInBucket * usdcWeight / totalUSDCWeight;
        if (amountInBucket > 0) {
            //Cant overflow since the amountInBucket is less than  or equal to the total amount in the bucket
            HOLDING_CONTRACT.addHolding(user, USDC, SafeCast.toUint192(amountInBucket));
        }

        {
            _revertIfGreater(glwWeight, totalGlwWeight, IMinerPool.GlowWeightGreaterThanTotalWeight.selector);
            uint256 amountGlowToSend = GLOW_REWARDS_PER_BUCKET * glwWeight / totalGlwWeight;
            if (amountGlowToSend > 0) {
                SafeERC20.safeTransfer(IERC20(address(GLOW_TOKEN)), user, amountGlowToSend);
            }
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                                 bucket delays                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IMinerPool
     */
    function delayBucketFinalization(uint256 bucketId) external {
        if (isBucketFinalized(bucketId)) {
            _revert(IGCA.BucketAlreadyFinalized.selector);
        }
        if (!IVetoCouncil(_VETO_COUNCIL).isCouncilMember(msg.sender)) {
            _revert(IMinerPool.CallerNotVetoCouncilMember.selector);
        }

        if (_buckets[bucketId].lastUpdatedNonce != slashNonce) {
            _revert(IMinerPool.CannotDelayBucketThatNeedsToUpdateSlashNonce.selector);
        }

        uint256 key = bucketId / 256;
        uint256 shift = bucketId % 256;
        uint256 existingBitmap = _bucketDelayedBitmap[key];
        uint256 bitmask = 1 << shift;
        if (existingBitmap & bitmask != 0) {
            _revert(IMinerPool.BucketAlreadyDelayed.selector);
        }
        _bucketDelayedBitmap[key] = existingBitmap | bitmask;
        //If the length is zero that means
        // the bucket has never been initialized
        // therefore, the veto council should not be able
        // to delay a bucket that has never been initialized
        if (_buckets[bucketId].reports.length == 0) {
            _revert(IMinerPool.CannotDelayEmptyBucket.selector);
        }

        _buckets[bucketId].finalizationTimestamp += SafeCast.toUint128(bucketDelayDuration());
    }

    /* -------------------------------------------------------------------------- */
    /*                                view functions                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns the bucket claim bitmap for a user
     * @param bucketId - the bucket id to check
     * @dev Each bit in the 256 bit word is a flag for whether the user has claimed from that bucket.
     * @dev for example, for bitmap with b'....0011'  with an input of any bucketId between `0-255` means that the user has claimed from buckets 0 and 1
     * @dev If `bucketId` is 256, the bitmap returned will start at bucketId 256 in the 0 binary slot.
     * @dev a few examples:
     *             `bucketId` = 12 returns the bitmap at position 0 which contains the flags for buckets 0-255
     *             `bucketId` = 256 returns the bitmap at position 1 which contains the flags for buckets 256- 511
     *             `bucketId` = 515 returns the bitmap at position 2 which contains the flags for buckets  512-767
     * @return bitmap - the bitmap in which the bucket claim flag is located for the `user`
     */
    function bucketClaimBitmap(uint256 bucketId, address user) public view returns (uint256) {
        return _getUserBitmapForBucket(bucketId, user);
    }

    /**
     * @inheritdoc IMinerPool
     */
    function hasBucketBeenDelayed(uint256 bucketId) external view returns (bool) {
        return _bucketDelayedBitmap[bucketId / 256] & (1 << (bucketId % 256)) != 0;
    }

    /**
     * @notice the early liquidity contract address
     * @return the early liquidity contract address
     */
    function earlyLiquidity() public view returns (address) {
        return _EARLY_LIQUIDITY;
    }

    /**
     * @inheritdoc IMinerPool
     */
    function createClaimRewardFromBucketDigest(
        uint256 bucketId,
        uint256 glwWeight,
        uint256 usdcWeight,
        uint256 index,
        bool claimFromInflation
    ) public view returns (bytes32) {
        return keccak256(
            abi.encodePacked(
                "\x19\x01",
                _domainSeparatorV4(),
                keccak256(
                    abi.encode(
                        _CLAIM_REWARD_FROM_BUCKET_TYPEHASH, bucketId, glwWeight, usdcWeight, index, claimFromInflation
                    )
                )
            )
        );
    }

    /**
     * @notice The amount of time a delay action will delay a bucket by
     * @return the amount of time a delay action will delay a bucket by
     */
    function bucketDelayDuration() public pure virtual returns (uint256) {
        return _BUCKET_DELAY_DURATION;
    }

    /* -------------------------------------------------------------------------- */
    /*                          internal state changing funcs                     */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice used internally to mint `amount` of GCC to the carbon credit auction contract
     * @dev each bucketId can only be used once to mint to the carbon credit auction
     * @dev the `_mintedToCarbonCreditAuctionBitmap` is used to track which buckets have already been used to mint to the carbon credit auction
     *             -   the key for the mapping is `bucketId / 256`
     *             -   where each slot stores a bitmap of the buckets that have been used to mint to the carbon credit auction
     * @dev if the bucket has already been used to mint to the carbon credit auction, the function continues
     *             -   this behaviour is necessary since the function is called on each claim
     *             -   this function's `trigger` is the `claimRewardMultipleRootsOneBucket` function
     *             -   it should also be able to be called publically
     */
    function _handleMintToCarbonCreditAuction(uint256 bucketId, uint256 amountToMint) internal {
        uint256 key = bucketId / _BITS_IN_UINT;
        uint256 existingBitmap = _mintedToCarbonCreditAuctionBitmap[key];
        uint256 shift = bucketId % _BITS_IN_UINT;
        uint256 mask = 1 << shift;
        if (mask & existingBitmap == 0) {
            existingBitmap |= mask;
            _mintedToCarbonCreditAuctionBitmap[key] = existingBitmap;
            GCC.mintToCarbonCreditAuction(bucketId, amountToMint);
        }
    }

    /**
     * @dev used internally to set the user bitmap for a bucket
     * @param bucketId - the id of the bucket
     *                         - this is divided by 256 to find the key in the mapping
     * @param user - the address of the user
     * @param userBitmap - the new bitmap to set for the user
     */
    function _setUserBitmapForBucket(uint256 bucketId, address user, uint256 userBitmap) internal {
        _bucketClaimBitmap[bucketId / _BITS_IN_UINT][user] = userBitmap;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 internal view                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @dev user internally to check if a user has already claimed for a bucket
     *             -   if the have already claimed, the function reverts
     *             -   if they have not claimed from the bucket, the function returns the new bitmap that should be stored
     * @param bucketId - the id of the bucket
     * @param userBitmap - the existing bitmap of the user
     * @return userBitmap - the new bitmap of the user
     */
    function _checkClaimAvailableAndReturnNewBitmap(uint256 bucketId, uint256 userBitmap)
        internal
        pure
        returns (uint256)
    {
        uint256 shift = (bucketId % _BITS_IN_UINT);
        uint256 mask = 1 << shift;
        if (mask & userBitmap != 0) _revert(IMinerPool.UserAlreadyClaimed.selector);
        userBitmap |= mask;
        return userBitmap;
    }

    /**
     * @dev used internally check if a proof is valid
     * @param payoutWallet - the address of the user
     * @param glwWeight - the weight of the user's glw rewards
     * @param usdcWeight - the weight of the user's USDC rewards
     * @param proof - the merkle proof of the user's rewards
     *                     - the leaves are {payoutWallet, glwWeight, usdcWeight}
     */
    function _checkProof(
        address payoutWallet,
        uint256 glwWeight,
        uint256 usdcWeight,
        bytes32[] calldata proof,
        bytes32 root
    ) internal pure {
        bytes32 leaf = keccak256(abi.encodePacked(payoutWallet, glwWeight, usdcWeight));

        if (!MerkleProofLib.verifyCalldata(proof, root, leaf)) {
            _revert(IMinerPool.InvalidProof.selector);
        }
    }

    /**
     * @dev checks to make sure the weights in the report
     *         - don't overflow the total weights that have been set for the bucket
     *         - Without this check, a malicious weight could be used to overflow the total weights
     *         - and grab rewards from other buckets
     * @param bucketId - the id of the bucket
     * @param totalGlwWeight - the total amount of glw weight for the bucket
     * @param totalUSDCWeight - the total amount of USDC weight for the bucket
     * @param glwWeight - the glw weight of the leaf in the report being claimed
     * @param usdcWeight - the USDC weight of the leaf in the report being claimed
     */
    function _checkWeightsForOverflow(
        uint256 bucketId,
        uint256 totalGlwWeight,
        uint256 totalUSDCWeight,
        uint256 glwWeight,
        uint256 usdcWeight
    ) internal {
        PushedWeights memory pushedWeights = _weightsPushed[bucketId];
        pushedWeights.pushedGlwWeight += SafeCast.toUint64(glwWeight);
        pushedWeights.pushedUSDCWeight += SafeCast.toUint64(usdcWeight);
        if (pushedWeights.pushedGlwWeight > totalGlwWeight) {
            _revert(IMinerPool.GlowWeightOverflow.selector);
        }
        if (pushedWeights.pushedUSDCWeight > totalUSDCWeight) {
            _revert(IMinerPool.USDCWeightOverflow.selector);
        }
        _weightsPushed[bucketId] = pushedWeights;
    }

    /**
     * @dev used internally to get the user bitmap for a bucket
     * @param bucketId - the id of the bucket
     *                 - this is divided by 256 to find the key in the mapping
     * @param user - the address of the user
     * @return userBitmap - the bitmap of the user
     */
    function _getUserBitmapForBucket(uint256 bucketId, address user) internal view returns (uint256) {
        return _bucketClaimBitmap[bucketId / _BITS_IN_UINT][user];
    }

    /**
     * @dev used internally to get the genesis timestamp
     *             - it must override the function in BucketSubmission
     * @return the genesis timestamp
     */
    function _genesisTimestamp() internal view override(BucketSubmission, GCA) returns (uint256) {
        return GENESIS_TIMESTAMP;
    }

    /**
     * @dev used to pass down the current week to the {GCASalaryHelper} contract
     */
    function _currentWeek() internal view override(GCA) returns (uint256) {
        return currentBucket();
    }

    /**
     * @dev used to pass down the domain separator to the {GCASalaryHelper} contract
     */
    function _domainSeperatorV4Main() internal view virtual override(GCA) returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @notice returns the bucket duration
     * @return bucketDuration - the bucket duration
     */
    function bucketDuration() internal pure virtual override(GCA, BucketSubmission) returns (uint256) {
        return _BUCKET_DURATION;
    }

    /**
     * @notice reverts with {selector} if {a} > {b}
     * @param a - the first number
     * @param b - the second number
     * @param selector - the selector to revert with
     */
    function _revertIfGreater(uint256 a, uint256 b, bytes4 selector) internal pure {
        if (a > b) _revert(selector);
    }

    /**
     * @dev efficient checker for whether an address is the zero address
     * @param addr the address to check
     * @return res - whether or not the address is the zero address
     */
    function _isZeroAddress(address addr) internal pure returns (bool res) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            res := iszero(addr)
        }
    }
}

// File: src/GLOW.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IGlow} from "./interfaces/IGlow.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {_GENESIS_TIMESTAMP} from "@/Constants/Constants.sol";
/**
 * @title Glow
 * @author DavidVorick
 * @author 0xSimon(twitter) - OxSimbo(github)
 * @notice The Glow token is the backbone of the protocol
 *         - Solar farms are rewarded with glow tokens as they produce solar
 *         - GCA's (Glow Certification Agents) and Veto Council Members are rewarded in GLOW
 *             - for their contributions
 *         - The Grants Treasury is rewarded in GLOW for their contributions
 *         - Holders can anchor (stake) glow to earn voting power in governance
 *             - anchoring lasts 5 years from the point of unstaking
 */

contract Glow is ERC20, ERC20Permit, IGlow {
    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /// @notice The cooldown period after unstaking before a user can claim their tokens
    uint256 private constant _STAKE_COOLDOWN_PERIOD = 365 days * 5;

    /// @notice The amount of GLW that is minted per second for the GCA and Miner Pool
    /// @notice 185,000 GLW per week
    /// @dev 175,000 to miners
    /// @dev 10,000 to the GCAs
    uint256 public constant GCA_AND_MINER_POOL_INFLATION_PER_SECOND = 185_000 * 1 ether / uint256(7 days);

    /// @notice The amount of GLW that is minted per second for the Veto Council
    /// @notice 5,000 GLW per week
    uint256 public constant VETO_COUNCIL_INFLATION_PER_SECOND = 5_000 * 1 ether / uint256(7 days);

    /// @notice The amount of GLW that is minted per second for the Grants Treasury
    /// @notice 40,000 GLW per week
    uint256 public constant GRANTS_TREASURY_INFLATION_PER_SECOND = 40_000 * 1 ether / uint256(7 days);

    /// @notice the maximum number of times a user can unstake without clearing their unstaked positions
    /// @notice before they are forced to wait 1 day before staking again
    uint256 public constant MAX_UNSTAKES_BEFORE_EMERGENCY_COOLDOWN = 100;

    /// @notice the cooldown period once users stake over 100 times
    uint256 public constant EMERGENCY_COOLDOWN_PERIOD = 1 days;

    /* -------------------------------------------------------------------------- */
    /*                                  immutables                                */
    /* -------------------------------------------------------------------------- */

    /// @notice The address of the Early Liquidity Contract
    //  solhint-disable-next-line var-name-mixedcase
    address public immutable EARLY_LIQUIDITY_ADDRESS;

    /// @notice the GCA And Miner Pool address
    address public immutable GCA_AND_MINER_POOL_ADDRESS;

    /// @notice the Veto Council address
    address public immutable VETO_COUNCIL_ADDRESS;

    /// @notice the Grants Treasury address
    address public immutable GRANTS_TREASURY_ADDRESS;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */
    /// @notice The last time the GCA and Miner Pool claimed GLW
    uint256 public gcaAndMinerPoolLastClaimedTimestamp;

    /// @notice The last time the Veto Council claimed GLW
    uint256 public vetoCouncilLastClaimedTimestamp;

    /// @notice The last time the Grants Treasury claimed GLW
    uint256 public grantsTreasuryLastClaimedTimestamp;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    /// @notice stores the total amount of GLOW staked by a user
    mapping(address => uint256) public numStaked;

    /// @notice stores the unstaked positions of a user
    mapping(address => mapping(uint256 => UnstakedPosition)) private _unstakedPositions;

    /// @notice stores the head of the unstaked positions of a user
    /// @dev the head is the last index with data. If we need to push, we push at head + 1
    /// @dev if the head is zero, there may or may not be data.
    mapping(address => Pointers) private _unstakedPositionPointers;

    /// @notice stores the last time a user staked in case the user has over 100 staked positions
    mapping(address => uint256) public emergencyLastUnstakeTimestamp;

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */

    /*
     * @notice Sets the immutable variables (GENESIS_TIMESTAMP, EARLY_LIQUIDITY_ADDRESS)
    * @notice sends 12 million GLW to the Early Liquidity Contract and 96 million GLW to the unlocker contract
    * @param _earlyLiquidityAddress The address of the Early Liquidity Contract
    * @param _vestingContract The address of the vesting contract
    * @param _gcaAndMinerPoolAddress The address of the GCA and Miner Pool
    * @param _vetoCouncilAddress The address of the Veto Council
    * @param _grantsTreasuryAddress The address of the Grants Treasury
    */
    constructor(
        address _earlyLiquidityAddress,
        address _vestingContract,
        address _gcaAndMinerPoolAddress,
        address _vetoCouncilAddress,
        address _grantsTreasuryAddress
    ) payable ERC20("Glow", "GLW-BETA") ERC20Permit("Glow") {
        EARLY_LIQUIDITY_ADDRESS = _earlyLiquidityAddress;
        GCA_AND_MINER_POOL_ADDRESS = _gcaAndMinerPoolAddress;
        VETO_COUNCIL_ADDRESS = _vetoCouncilAddress;
        GRANTS_TREASURY_ADDRESS = _grantsTreasuryAddress;
        _handleConstructorMint(_earlyLiquidityAddress, _vestingContract, _grantsTreasuryAddress);
    }

    /* -------------------------------------------------------------------------- */
    /*                                  staking                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IGlow
     * @dev if the user has unstaked positions that have already expired,
     *         -   the function will auto claim those tokens for the user
     */
    function stake(uint256 stakeAmount) external {
        //Cannot stake zero tokens
        if (stakeAmount == 0) _revert(IGlow.CannotStakeZeroTokens.selector);

        //Find head tail in the mapping
        IGlow.Pointers memory pointers = _unstakedPositionPointers[msg.sender];
        uint256 head = pointers.head;

        //Init the unstakedTotal
        uint256 amountInUserUnstakePool;

        //Init the new head
        uint256 newHead = head;

        uint256 tail = pointers.tail;

        //We need to loop through starting from the head (newest positions)
        for (uint256 i = head; i >= tail; --i) {
            //load the posiiton from storage into memory
            UnstakedPosition memory position = _unstakedPositions[msg.sender][i];

            //increase the amount in the user unstake pool
            //by the amount that is in the position we are on
            amountInUserUnstakePool += position.amount;

            //If it's exactly equal, that means the data will be fully cleared
            //And the head moves to i-1 or 0(if fully empty now)
            if (amountInUserUnstakePool == stakeAmount) {
                //If i is 0 and the amount is exactly zero,
                //that means we can restart the unstaked positions from scratch
                if (i == 0) {
                    newHead = 0;
                    delete _unstakedPositions[msg.sender][newHead];
                }
                //If i is not zero, we can just move the head to i-1
                else {
                    newHead = i - 1;
                }
                break;
            }

            //If the amount in the user unstake pool is greater than the stake amount
            //That means we overshot and we need to pull back the amount we overshot by
            if (amountInUserUnstakePool > stakeAmount) {
                uint256 overshoot = amountInUserUnstakePool - stakeAmount;
                //Let;s say we are at 49 in the stake pool, and then the current position has 10.
                //and we wanted to stake a total of 50
                //Once we add the amount in this pool, we have a total of 59 in the stake pool amount.
                //That means we overshot by 59-50, and the new amount in the stake pool
                //Should be the overshot amount.
                //Instead of having 10 in the latest pool, we have 9 since we needed to pull 1
                newHead = i; //If we overshot, the head stays the same and it does indeed still have data
                _unstakedPositions[msg.sender][i].amount = SafeCast.toUint192(overshoot);
                break;
            }

            //If we have reached the tail (oldest position) and we still haven't overshot
            //We delete the tail
            if (i == tail) {
                if (stakeAmount > amountInUserUnstakePool) {
                    delete _unstakedPositions[msg.sender][tail];
                }
                newHead = tail;
                break;
            }
        }

        //If the new head is not equal to the old head, we update the head in storage
        //We use this equality check to prevent redundant sstores
        if (newHead != head) {
            _unstakedPositionPointers[msg.sender].head = SafeCast.toUint128(newHead);
        }

        //If the stake amount is greater than the amount in the user unstake pool
        //Then we need to transfer the difference from the user to the contract
        if (stakeAmount > amountInUserUnstakePool) {
            uint256 amountGlowToTransfer = stakeAmount - amountInUserUnstakePool;
            _transfer(msg.sender, address(this), amountGlowToTransfer);
        }

        //Note: We don't handle the zero case since that would be a redundant transfer

        //Increase the number of tokens staked by the user
        numStaked[msg.sender] += stakeAmount;
        //Emit the Stake event
        emit IGlow.Stake(msg.sender, stakeAmount);
    }

    /**
     * @inheritdoc IGlow
     */
    function unstake(uint256 amount) external {
        //Revert on zero amount
        if (amount == 0) _revert(IGlow.CannotUnstakeZeroTokens.selector);

        //Load the number of tokens staked by the user
        uint256 numAccountStaked = numStaked[msg.sender];

        //if the user is unstaking more than they have staked, we revert
        if (amount > numAccountStaked) {
            _revert(IGlow.UnstakeAmountExceedsStakedBalance.selector);
        }

        //Find the length of the unstaked positions starting at the tail
        //This gives us the # of unstaked positions that the user has
        IGlow.Pointers memory pointers = _unstakedPositionPointers[msg.sender];
        uint256 adjustedLenBefore = pointers.head - pointers.tail + 1;

        uint256 indexInMappingToPushTo = pointers.head + 1;
        if (pointers.head == pointers.tail) {
            if (_unstakedPositions[msg.sender][pointers.head].amount == 0) {
                adjustedLenBefore = 0;
                indexInMappingToPushTo = pointers.head;
            }
        }

        //if adjustlenBefore >= 99
        // we + 2 to proactively set emergencyLastUpdate when length will be 99 so the 100th unstake will trigger cooldown
        if (adjustedLenBefore + 2 > MAX_UNSTAKES_BEFORE_EMERGENCY_COOLDOWN) {
            uint256 lastUnstakedTimestamp = emergencyLastUnstakeTimestamp[msg.sender];

            //Handle the zero case
            if (lastUnstakedTimestamp == 0) {
                emergencyLastUnstakeTimestamp[msg.sender] = block.timestamp;
                // if the user has unstaked before, we need to check if they are in cooldown
            } else if (block.timestamp - lastUnstakedTimestamp < EMERGENCY_COOLDOWN_PERIOD) {
                _revert(IGlow.UnstakingOnEmergencyCooldown.selector);
                // if the user is not in cooldown, we need to update the timestamp
            } else {
                emergencyLastUnstakeTimestamp[msg.sender] = block.timestamp;
            }
        }

        //Decrease the number of tokens staked by the user
        numStaked[msg.sender] = numAccountStaked - amount;

        _unstakedPositions[msg.sender][indexInMappingToPushTo] = UnstakedPosition({
            amount: SafeCast.toUint192(amount),
            cooldownEnd: SafeCast.toUint64(block.timestamp + _STAKE_COOLDOWN_PERIOD)
        });

        pointers = Pointers({head: SafeCast.toUint128(indexInMappingToPushTo), tail: pointers.tail});

        _unstakedPositionPointers[msg.sender] = pointers;
        emit IGlow.Unstake(msg.sender, amount);
    }

    /**
     * @inheritdoc IGlow
     */
    function claimUnstakedTokens(uint256 amount) external {
        //Cannot claim zero tokens
        if (amount == 0) _revert(IGlow.CannotClaimZeroTokens.selector);
        uint256 claimableTotal;

        //Cache len]0
        IGlow.Pointers memory pointers = _unstakedPositionPointers[msg.sender];

        uint256 head = pointers.head;
        uint256 tail = pointers.tail;
        uint256 newTail = tail;

        //Loop through the unstaked positions until claimableTotal >= amount
        //Tail will also be <= len so no risk of underflow
        //Tail should also remain close to len since we delete unstaked positions as we claim them
        //and we restrict the number of unstaked positions to 100 before a cooldown is enforced on the user

        for (uint256 i = tail; i <= head; ++i) {
            //Read the position from storage
            UnstakedPosition storage position = _unstakedPositions[msg.sender][i];
            //if block.timestamp <= position.cooldownEnd
            //If the position is not ready to be claimed, we revert
            //  -   this is so because we can't claim tokens that are not ready to be claimed
            //  -   and positions are chronologically ordered, so if one position is not ready to be claimed,
            //  -   all following positions are not ready to be claimed
            //  -   therefore, we can revert early since we'll never have enough tokens to fulfill the claim
            if (position.cooldownEnd >= block.timestamp) {
                _revert(IGlow.InsufficientClaimableBalance.selector);
            }

            //Increment the claimableTotal by the position amount
            claimableTotal += position.amount;

            //If the claimableTotal is equal to the amount, we need to delete the old position and increment the newTail
            // - since the old unstaked positions EXACTLY fulfill the amount
            if (claimableTotal == amount) {
                newTail = i + 1;
                if (newTail > head) {
                    newTail = head;
                }
                //Update the tail in storage
                _unstakedPositionPointers[msg.sender] =
                    Pointers({head: SafeCast.toUint128(head), tail: SafeCast.toUint128(newTail)});
                //delete the position for a gas refund
                delete _unstakedPositions[msg.sender][i];
                //transfer the amount to the user
                _transfer(address(this), msg.sender, amount);
                //emit the claim event
                emit IGlow.ClaimUnstakedGLW(msg.sender, amount);
                return;
            }

            //If the claimableTotal is greater than the amount, we need to  deduct from the position in storage
            // and the tail will stay the same since the unstaked position still has some tokens left
            if (claimableTotal > amount) {
                //New tail is equal to i
                newTail = i;
                //Check redundancy before sstoring the new tail
                if (newTail != tail) {
                    _unstakedPositionPointers[msg.sender] =
                        Pointers({head: SafeCast.toUint128(head), tail: SafeCast.toUint128(newTail)});
                }
                //Calculate the amount that is left in the position after the claim
                uint256 amountLeftInPosition = claimableTotal - amount;
                //Update the position amount in storage
                position.amount = SafeCast.toUint192(amountLeftInPosition);
                //Transfer the amount to the user
                _transfer(address(this), msg.sender, amount);
                //Emit the claim event
                emit IGlow.ClaimUnstakedGLW(msg.sender, amount);
                return;
            }

            //When looping, we delete all unstaked positions that are consumed
            // as part of the token claim
            delete _unstakedPositions[msg.sender][i];
        }

        _revert(IGlow.InsufficientClaimableBalance.selector);
    }

    /* -------------------------------------------------------------------------- */
    /*                                  inflation                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IGlow
     */
    function claimGLWFromGCAAndMinerPool() external returns (uint256) {
        //If the address is not set, we revert
        if (_isZeroAddress(GCA_AND_MINER_POOL_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        //If the caller is not the GCA and Miner Pool, we revert
        if (msg.sender != GCA_AND_MINER_POOL_ADDRESS) _revert(IGlow.CallerNotGCA.selector);
        //Read the timestamp from storage
        uint256 timestampInStorage = gcaAndMinerPoolLastClaimedTimestamp;
        //If the timestamp is zero, we set it to the genesis timestamp
        // else we set it to the timestamp in storage
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        //Calculate the seconds since the last claim
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        //Calculate the amount to claim
        uint256 amountToClaim = secondsSinceLastClaim * GCA_AND_MINER_POOL_INFLATION_PER_SECOND;
        //If the amount to claim is zero, we return zero and exit
        if (amountToClaim == 0) return 0;
        //if the amount is not zero, we update the timestamp in storage
        gcaAndMinerPoolLastClaimedTimestamp = block.timestamp;
        //and we mint the amount to the GCA and Miner Pool
        _mint(GCA_AND_MINER_POOL_ADDRESS, amountToClaim);
        //we then return the amount to claim
        return amountToClaim;
    }

    /**
     * @inheritdoc IGlow
     */
    function claimGLWFromVetoCouncil() external returns (uint256) {
        //If the address is not set, we revert
        if (_isZeroAddress(VETO_COUNCIL_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        //If the caller is not the Veto Council, we revert
        if (msg.sender != VETO_COUNCIL_ADDRESS) _revert(IGlow.CallerNotVetoCouncil.selector);
        //Read the timestamp from storage
        uint256 timestampInStorage = vetoCouncilLastClaimedTimestamp;
        //If the timestamp is zero, we set it to the genesis timestamp
        // else we set it to the timestamp in storage
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        //Calculate the seconds since the last claim
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        //Calculate the amount to claim
        uint256 amountToClaim = secondsSinceLastClaim * VETO_COUNCIL_INFLATION_PER_SECOND;
        //If the amount to claim is zero, we return zero and exit
        if (amountToClaim == 0) return 0;
        //if the amount is not zero, we update the timestamp in storage
        vetoCouncilLastClaimedTimestamp = block.timestamp;
        //and we mint the amount to the Veto Council
        _mint(VETO_COUNCIL_ADDRESS, amountToClaim);
        //we then return the amount to claim
        return amountToClaim;
    }

    /**
     * @inheritdoc IGlow
     */
    function claimGLWFromGrantsTreasury() external returns (uint256) {
        //If the address is not set, we revert
        if (_isZeroAddress(GRANTS_TREASURY_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        //If the caller is not the Grants Treasury, we revert
        if (msg.sender != GRANTS_TREASURY_ADDRESS) _revert(IGlow.CallerNotGrantsTreasury.selector);
        //Read the timestamp from storage
        uint256 timestampInStorage = grantsTreasuryLastClaimedTimestamp;
        //If the timestamp is zero, we set it to the genesis timestamp
        // else we set it to the timestamp in storage
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        //Calculate the seconds since the last claim
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        //Calculate the amount to claim
        uint256 amountToClaim = secondsSinceLastClaim * GRANTS_TREASURY_INFLATION_PER_SECOND;
        //If the amount to claim is zero, we return zero and exit
        if (amountToClaim == 0) return 0;
        //if the amount is not zero, we update the timestamp in storage
        grantsTreasuryLastClaimedTimestamp = block.timestamp;
        //and we mint the amount to the Grants Treasury
        _mint(GRANTS_TREASURY_ADDRESS, amountToClaim);
        //we then return the amount to claim
        return amountToClaim;
    }

    /* -------------------------------------------------------------------------- */
    /*                                view functions                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc IGlow
     */
    function unstakedPositionsOf(address account) external view returns (UnstakedPosition[] memory) {
        IGlow.Pointers memory pointers = _unstakedPositionPointers[account];
        uint256 start = pointers.tail;
        uint256 end = pointers.head + 1;
        UnstakedPosition[] memory positions = new UnstakedPosition[](end - start);

        if (pointers.tail == pointers.head) {
            UnstakedPosition memory position = _unstakedPositions[account][pointers.head];
            if (position.amount == 0) {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    mstore(positions, 0)
                }
                return positions;
            }
            positions[0] = position;
            ++start;
        }
        unchecked {
            //Start is always less than end so no risk of underflow
            //start should also be close to end since we delete unstaked positions as we claim them
            // and we restrict the number of unstaked positions to 100 before a cooldown is enforced on the user
            for (uint256 i = start; i < end; ++i) {
                UnstakedPosition memory position = _unstakedPositions[account][i];
                //If the tail is zero and the amount is zero, that means
                //There has never been a stake, because if there had been a stake,
                //The amount wouldn't be empty,
                //And if the amount is empty that means that there has been a claim on that position
                //And the tail would not be zero
                if (i == 0) {
                    if (position.amount == 0) {
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            //set the length to 0 in memory
                            mstore(positions, 0)
                        }
                        break;
                    }
                }
                //No addition, therefore no risk of overflow
                //i always >= start so no risk of underflow
                positions[i - start] = position;
            }
            return positions;
        }
    }

    /**
     * @notice returns the tail of the unstaked positions for the user
     * @param account the account to get the tail for
     * @return the tail of the unstaked positions for the user
     */
    function accountUnstakedPositionPointers(address account) external view returns (IGlow.Pointers memory) {
        return _unstakedPositionPointers[account];
    }

    /**
     * @inheritdoc IGlow
     */

    function unstakedPositionsOf(address account, uint256 start, uint256 end)
        external
        view
        returns (UnstakedPosition[] memory)
    {
        IGlow.Pointers memory pointers = _unstakedPositionPointers[account];
        start = start + pointers.tail;
        end = end + pointers.tail;
        if (end > pointers.head + 1) {
            end = pointers.head + 1;
        }

        //If the start is greater than the end, we return an empty array
        if (start >= end) {
            return new UnstakedPosition[](0);
        }
        UnstakedPosition[] memory positions = new UnstakedPosition[](end - start);

        if (pointers.tail == pointers.head) {
            UnstakedPosition memory position = _unstakedPositions[account][pointers.head];
            if (position.amount == 0) {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    mstore(positions, 0)
                }
                return positions;
            }
            positions[0] = position;
            ++start;
        }

        unchecked {
            //Start is always less than end so no risk of underflow
            //start should also be close to end since we delete unstaked positions as we claim them
            // and we restrict the number of unstaked positions to 100 before a cooldown is enforced on the user
            for (uint256 i = start; i < end; ++i) {
                UnstakedPosition memory position = _unstakedPositions[account][i];
                //If the tail is zero and the amount is zero, that means
                //There has never been a stake, because if there had been a stake,
                //The amount wouldn't be empty,
                //And if the amount is empty that means that there has been a claim on that position
                //And the tail would not be zero
                if (i == 0) {
                    if (position.amount == 0) {
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            //set the length to 0 in memory
                            mstore(positions, 0)
                        }
                        break;
                    }
                }
                //No addition, therefore no risk of overflow
                //i always >= start so no risk of underflow
                positions[i - start] = position;
            }
            return positions;
        }
    }

    /**
     * @inheritdoc IGlow
     */
    function gcaInflationData() external view returns (uint256, uint256 totalAlreadyClaimed, uint256 totalToClaim) {
        if (_isZeroAddress(GCA_AND_MINER_POOL_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        uint256 timestampInStorage = gcaAndMinerPoolLastClaimedTimestamp;
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        totalToClaim = secondsSinceLastClaim * GCA_AND_MINER_POOL_INFLATION_PER_SECOND;
        totalAlreadyClaimed = timestampToClaimFrom - GENESIS_TIMESTAMP();
        return (timestampInStorage, totalAlreadyClaimed, totalToClaim);
    }

    /**
     * @inheritdoc IGlow
     */
    function vetoCouncilInflationData()
        external
        view
        returns (uint256, uint256 totalAlreadyClaimed, uint256 totalToClaim)
    {
        if (_isZeroAddress(VETO_COUNCIL_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        uint256 timestampInStorage = vetoCouncilLastClaimedTimestamp;
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        totalToClaim = secondsSinceLastClaim * VETO_COUNCIL_INFLATION_PER_SECOND;
        totalAlreadyClaimed = timestampToClaimFrom - GENESIS_TIMESTAMP();
        return (timestampInStorage, totalAlreadyClaimed, totalToClaim);
    }

    /**
     * @inheritdoc IGlow
     */
    function grantsTreasuryInflationData()
        external
        view
        returns (uint256, uint256 totalAlreadyClaimed, uint256 totalToClaim)
    {
        if (_isZeroAddress(GRANTS_TREASURY_ADDRESS)) _revert(IGlow.AddressNotSet.selector);
        uint256 timestampInStorage = grantsTreasuryLastClaimedTimestamp;
        uint256 timestampToClaimFrom = timestampInStorage == 0 ? GENESIS_TIMESTAMP() : timestampInStorage;
        uint256 secondsSinceLastClaim = block.timestamp - timestampToClaimFrom;
        totalToClaim = secondsSinceLastClaim * GRANTS_TREASURY_INFLATION_PER_SECOND;
        totalAlreadyClaimed = timestampToClaimFrom - GENESIS_TIMESTAMP();
        return (timestampInStorage, totalAlreadyClaimed, totalToClaim);
    }

    /* -------------------------------------------------------------------------- */
    /*                                one time setters                            */
    /* -------------------------------------------------------------------------- */

    /* -------------------------------------------------------------------------- */
    /*                                  getters                                   */
    /* -------------------------------------------------------------------------- */
    /// @notice The timestamp of the genesis block
    function GENESIS_TIMESTAMP() public view virtual returns (uint256) {
        return _GENESIS_TIMESTAMP;
    }

    /* -------------------------------------------------------------------------- */
    /*                       constructor mint virtual                             */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Mints the initial supply of GLOW
     * @param _earlyLiquidityAddress The address of the early liquidity contract
     * @param _vestingContract The address of the vesting contract
     * @param _grantsTreasryAddress The address of the grants treasury
     */
    function _handleConstructorMint(
        address _earlyLiquidityAddress,
        address _vestingContract,
        address _grantsTreasryAddress
    ) internal virtual {
        _mint(_earlyLiquidityAddress, 12_000_000 ether);
        _mint(_vestingContract, 96_000_000 ether);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 privte utils                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Returns the smaller of two numbers
     * @param a The first number
     * @param b The second number
     */
    function _min(uint256 a, uint256 b) private pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @notice More efficiently reverts with a bytes4 selector
     * @param selector The selector to revert with
     */
    function _revert(bytes4 selector) internal pure {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }

    /**
     * @notice More efficient address(0) check
     */
    function _isZeroAddress(address _address) internal pure returns (bool isZero) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            isZero := iszero(_address)
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/access/Ownable.sol



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

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: src/interfaces/IVetoCouncil.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IVetoCouncil {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                    */
    /* -------------------------------------------------------------------------- */
    error CallerNotGovernance();
    error NoRewards();
    error ZeroAddressInConstructor();
    error MaxCouncilMembersExceeded();

    /* -------------------------------------------------------------------------- */
    /*                                   events                                    */
    /* -------------------------------------------------------------------------- */

    /**
     * @param oldMember The address of the member to be slashed or removed
     * @param newMember The address of the new member (0 = no new member)
     * @param slashOldMember Whether to slash the member or not
     */
    event VetoCouncilSeatsEdited(address indexed oldMember, address indexed newMember, bool slashOldMember);

    /**
     * @dev emitted when a council member is paid out
     * @param account The address of the council member
     * @param amountNow The amount paid out now
     * @param amountToBeVested The amount to be vested
     */
    event CouncilMemberPayout(address indexed account, uint256 amountNow, uint256 amountToBeVested);
    /* -------------------------------------------------------------------------- */
    /*                                 state-changing                             */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Add or remove a council member
     * @param oldMember The address of the member to be slashed or removed
     * @param newMember The address of the new member (0 = no new member)
     * @param slashOldMember Whether to slash the member or not
     * @return - true if the council member was added or removed, false if nothing was done
     *                 - the function should return false if the new member is already a council member
     *                 - if the old member is not a council member, the function should return false
     *                 - if the old member is a council member and the new member is the same as the old member, the function should return false
     *                 - by adding a new member there would be more than 7 council members, the function should return false
     */

    function addAndRemoveCouncilMember(address oldMember, address newMember, bool slashOldMember)
        external
        returns (bool);

    /**
     * @notice Payout the council member
     * @param member The address of the council member
     * @param nonce The payout nonce to claim from
     * @param sync Whether to sync the vesting schedule or not
     * @param members The addresses of the council members that were active at `nonce`
     */
    function claimPayout(address member, uint256 nonce, bool sync, address[] memory members) external;

    /* -------------------------------------------------------------------------- */
    /*                                   view                                    */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns true if the member is a council member
     * @param member The address of the member to be checked
     * @return - true if the member is a council member
     */
    function isCouncilMember(address member) external view returns (bool);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol



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

pragma solidity ^0.8.20;

/**
 * @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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// File: lib/openzeppelin-contracts/contracts/utils/Context.sol



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

pragma solidity ^0.8.20;

/**
 * @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: lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol



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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol



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

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError, bytes32) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol



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

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant _TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    string private _nameFallback;
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /**
     * @dev See {IERC-5267}.
     */
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _name which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal view returns (string memory) {
        return _name.toStringWithFallback(_nameFallback);
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _version which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal view returns (string memory) {
        return _version.toStringWithFallback(_versionFallback);
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/Nonces.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @dev Provides tracking nonces for addresses. Nonces will only increment.
 */
abstract contract Nonces {
    /**
     * @dev The nonce used for an `account` is not the expected current nonce.
     */
    error InvalidAccountNonce(address account, uint256 currentNonce);

    mapping(address account => uint256) private _nonces;

    /**
     * @dev Returns an the next unused nonce for an address.
     */
    function nonces(address owner) public view virtual returns (uint256) {
        return _nonces[owner];
    }

    /**
     * @dev Consumes a nonce.
     *
     * Returns the current value and increments nonce.
     */
    function _useNonce(address owner) internal virtual returns (uint256) {
        // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
        // decremented or reset. This guarantees that the nonce never overflows.
        unchecked {
            // It is important to do x++ and not ++x here.
            return _nonces[owner]++;
        }
    }

    /**
     * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
     */
    function _useCheckedNonce(address owner, uint256 nonce) internal virtual returns (uint256) {
        uint256 current = _useNonce(owner);
        if (nonce != current) {
            revert InvalidAccountNonce(owner, current);
        }
        return current;
    }
}

// File: src/interfaces/IUniswapV2Pair.sol



pragma solidity ^0.8.0;

interface IUniswapV2Pair {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);
    function totalSupply() external view returns (uint256);
    function balanceOf(address owner) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function PERMIT_TYPEHASH() external pure returns (bytes32);
    function nonces(address owner) external view returns (uint256);

    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
        external;

    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint256);
    function factory() external view returns (address);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function price0CumulativeLast() external view returns (uint256);
    function price1CumulativeLast() external view returns (uint256);
    function kLast() external view returns (uint256);

    function mint(address to) external returns (uint256 liquidity);
    function burn(address to) external returns (uint256 amount0, uint256 amount1);
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
    function skim(address to) external;
    function sync() external;

    function initialize(address, address) external;
}

// File: src/interfaces/IGCC.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IGCC is IERC20 {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                  */
    /* -------------------------------------------------------------------------- */
    error CallerNotGCAContract();
    error BucketAlreadyMinted();
    error CommitPermitSignatureExpired();
    error CommitSignatureInvalid();
    error CommitAllowanceUnderflow();
    error MustIncreaseCommitAllowanceByAtLeastOne();
    error CannotReferSelf();
    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */

    /**
     * @param lastUpdatedTimestamp - the last timestamp a user earned or used nominations
     * @ param amount - the amount of nominations a user has
     */
    struct Nominations {
        uint64 lastUpdatedTimestamp;
        uint192 amount;
    }

    /* -------------------------------------------------------------------------- */
    /*                                   events                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice is emitted when a user commits credits
     * @param account the account that committed credits
     * @param rewardAddress the address that earned the credits and nominations
     * @param gccAmount the amount of credits committed
     * @param usdcEffect the amount of USDC effect
     * @param impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     * @param referralAddress the address that referred the account
     *             - zero address if no referral
     */
    event GCCCommitted(
        address indexed account,
        address indexed rewardAddress,
        uint256 gccAmount,
        uint256 usdcEffect,
        uint256 impactPower,
        address referralAddress
    );

    /**
     * @notice is emitted when a user commits USDC
     * @param account the account that commit the USDC
     * @param rewardAddress the address that earns nominations
     * @param amount the amount of USDC commit
     * @param impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     * @param referralAddress the address that referred the account
     *             - zero address if no referral
     */
    event USDCCommitted(
        address indexed account,
        address indexed rewardAddress,
        uint256 amount,
        uint256 impactPower,
        address referralAddress
    );

    /**
     * @notice is emitted when a user approves a spender to commit credits on their behalf
     * @param account the account that approved a spender
     * @param spender the address of the spender
     * @param value -  new total allowance
     */
    event CommitGCCAllowance(address indexed account, address indexed spender, uint256 value);

    /* -------------------------------------------------------------------------- */
    /*                                   commits                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice allows a user to commit credits
     * @param amount the amount of credits to commit
     * @param rewardAddress the address to commit the credits to
     *     -   Rewards Address earns:
     *     -       1.  Carbon Neutrality
     *     -       2.  Nominations
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     * @return usdcEffect the amount of USDC used in the LP position
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCC(uint256 amount, address rewardAddress, uint256 minImpactPower)
        external
        returns (uint256 usdcEffect, uint256 impactPower);

    /**
     * @notice allows a user to commit credits
     * @param amount the amount of credits to commit
     * @param rewardAddress the address to commit the credits to
     *     -   Rewards Address earns:
     *     -       1.  Carbon Neutrality
     *     -       2.  Nominations
     * @param referralAddress the address that referred the account
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return usdcEffect the amount of USDC used in the LP position
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCC(uint256 amount, address rewardAddress, address referralAddress, uint256 minImpactPower)
        external
        returns (uint256 usdcEffect, uint256 impactPower);

    /**
     * @notice the entry point for an approved entity to commit credits on behalf of a user
     * @param from the address of the user to commit credits from
     * @param rewardAddress the address of the reward address to commit credits to
     *         - Carbon Neutrality
     *         - Nominations
     * @param amount the amount of credits to commit
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return usdcEffect the amount of USDC used in the LP position
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCCFor(address from, address rewardAddress, uint256 amount, uint256 minImpactPower)
        external
        returns (uint256, uint256);

    /**
     * @notice the entry point for an approved entity to commit credits on behalf of a user
     * @param from the address of the user to commit credits from
     * @param rewardAddress the address of the reward address to commit credits to
     *         - Carbon Neutrality
     *         - Nominations
     * @param amount the amount of credits to commit
     * @param referralAddress - the address that referred the account
     * @param usdcEffect the amount of USDC used in the LP position
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @param impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCCFor(
        address from,
        address rewardAddress,
        uint256 amount,
        address referralAddress,
        uint256 minImpactPower
    ) external returns (uint256 usdcEffect, uint256 impactPower);

    /**
     * @notice the entry point for an approved entity to commit credits on behalf of a user using EIP712 signatures
     * @param from the address of the user to commit credits from
     * @param rewardAddress the address of the reward address to commit credits to
     *         - Carbon Neutrality
     *         - Nominations
     * @param amount the amount of credits to commit
     * @param deadline the deadline for the signature
     * @param signature - the signature
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return usdcEffect the amount of USDC used in the LP position
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCCForAuthorized(
        address from,
        address rewardAddress,
        uint256 amount,
        uint256 deadline,
        bytes calldata signature,
        uint256 minImpactPower
    ) external returns (uint256 usdcEffect, uint256 impactPower);

    /**
     * @notice the entry point for an approved entity to commit credits on behalf of a user using EIP712 signatures
     * @param from the address of the user to commit credits from
     * @param rewardAddress the address of the reward address to commit credits to
     *         - Carbon Neutrality
     *         - Nominations
     * @param amount the amount of credits to commit
     * @param deadline the deadline for the signature
     * @param signature - the signature
     * @param referralAddress - the address that referred the account
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return usdcEffect the amount of USDC used in the LP position
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitGCCForAuthorized(
        address from,
        address rewardAddress,
        uint256 amount,
        uint256 deadline,
        bytes calldata signature,
        address referralAddress,
        uint256 minImpactPower
    ) external returns (uint256 usdcEffect, uint256 impactPower);

    /**
     * @notice Allows a user to commit USDC
     * @param amount the amount of USDC to commit
     * @param rewardAddress the address to commit the USDC to
     * @param referralAddress the address that referred the account
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitUSDC(uint256 amount, address rewardAddress, address referralAddress, uint256 minImpactPower)
        external
        returns (uint256 impactPower);

    /**
     * @notice Allows a user to commit USDC
     * @param amount the amount of USDC to commit
     * @param rewardAddress the address to commit the USDC to
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitUSDC(uint256 amount, address rewardAddress, uint256 minImpactPower)
        external
        returns (uint256 impactPower);

    /**
     * @notice Allows a user to commit USDC using permit
     * @param amount the amount of USDC to commit
     * @param rewardAddress the address to commit the USDC to
     * @param referralAddress the address that referred the account
     * @param deadline the deadline for the signature
     * @param v the v value of the signature for permit
     * @param r the r value of the signature for permit
     * @param s the s value of the signature for permit
     * @param minImpactPower - the minimum amount of impact power to receive from the commitment
     *
     * @return impactPower - sqrt(amount gcc used in lp * amountc usdc used in lp) aka nominations granted
     */
    function commitUSDCSignature(
        uint256 amount,
        address rewardAddress,
        address referralAddress,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint256 minImpactPower
    ) external returns (uint256 impactPower);

    /* -------------------------------------------------------------------------- */
    /*                                   minting                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice allows gca contract to mint GCC to the carbon credit auction
     * @dev must callback to the carbon credit auction contract so it can organize itself
     * @dev a bucket can only be minted from once
     * @param bucketId the id of the bucket to mint from
     * @param amount the amount of GCC to mint
     */
    function mintToCarbonCreditAuction(uint256 bucketId, uint256 amount) external;

    /* -------------------------------------------------------------------------- */
    /*                                   view functions                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns a boolean indicating if the bucket has been minted
     * @return if the bucket has been minted
     */
    function isBucketMinted(uint256 bucketId) external view returns (bool);

    /**
     * @notice direct setter to set transfer allowance and committing allowance in one transaction for a {spender}
     * @param spender the address of the spender to set the allowances for
     * @param transferAllowance the amount of transfer allowance to set
     * @param committingAllowance the amount of committing allowance to set
     */
    function setAllowances(address spender, uint256 transferAllowance, uint256 committingAllowance) external;

    /**
     * @notice approves a spender to commit credits on behalf of the caller
     * @param spender the address of the spender
     * @param amount the amount of credits to approve
     */
    function increaseCommitAllowance(address spender, uint256 amount) external;

    /**
     * @notice decreases a spender's allowance to commit credits on behalf of the caller
     * @param spender the address of the spender
     * @param amount the amount of credits to decrease the allowance by
     */
    function decreaseCommitAllowance(address spender, uint256 amount) external;

    /**
     * @notice allows a user to increase the erc20 and committing allowance of a spender in one transaction
     * @param spender the address of the spender
     * @param addedValue the amount of credits to increase the allowance by
     */
    function increaseAllowances(address spender, uint256 addedValue) external;

    /**
     * @notice allows a user to decrease the erc20 and committing allowance of a spender in one transaction
     * @param spender the address of the spender
     * @param requestedDecrease the amount of credits to decrease the allowance by
     */
    function decreaseAllowances(address spender, uint256 requestedDecrease) external;

    /**
     * @notice returns the committing allowance for a user
     * @param account the address of the account to check
     * @param spender the address of the spender to check
     * @return the committing allowance
     */
    function commitAllowance(address account, address spender) external view returns (uint256);

    /**
     * @notice returns the next nonce to be used when committing credits
     *         - only applies when the user is using EIP712 signatures similar to Permit
     * @param account the address of the account to check
     */
    function nextCommitNonce(address account) external view returns (uint256);
}

// File: src/interfaces/ICarbonCreditAuction.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface ICarbonCreditAuction {
    /* -------------------------------------------------------------------------- */
    /*                                   state-changing                           */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice receives GCC from the miner pool
     * @param amount the amount of GCC to receive
     * @dev this function can only be called by the miner pool contract
     */
    function receiveGCC(uint256 amount) external;
    /**
     * @notice purchases {unitsToBuy} units of GCC at a maximum price of {maxPricePerUnit} GLOW per unit
     * @param unitsToBuy the number of units to buy
     * @param maxPricePerUnit the maximum price per unit that the user is willing to pay
     */
    function buyGCC(uint256 unitsToBuy, uint256 maxPricePerUnit) external;

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice returns the price per unit of GCC
     */
    function getPricePerUnit() external view returns (uint256);

    /**
     * @notice returns the total supply of GCC available for sale in WEI
     * @dev this is not to be confused with the total units of GCC available for sale
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice returns the number of units of GCC available for sale
     */
    function unitsForSale() external view returns (uint256);

    /**
     * @notice returns the cumulative total number of units of GCC that have been sold or are available for sale
     */
    function totalSaleUnits() external view returns (uint256);
}

// File: lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol



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

pragma solidity ^0.8.20;

import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";

/**
 * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA
 * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like
 * Argent and Safe Wallet (previously Gnosis Safe).
 */
library SignatureChecker {
    /**
     * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the
     * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
        (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature);
        return
            (error == ECDSA.RecoverError.NoError && recovered == signer) ||
            isValidERC1271SignatureNow(signer, hash, signature);
    }

    /**
     * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
     * against the signer smart contract using ERC1271.
     *
     * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus
     * change through time. It could return true at block N and false at block N+1 (or the opposite).
     */
    function isValidERC1271SignatureNow(
        address signer,
        bytes32 hash,
        bytes memory signature
    ) internal view returns (bool) {
        (bool success, bytes memory result) = signer.staticcall(
            abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
        );
        return (success &&
            result.length >= 32 &&
            abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
    }
}

// File: src/interfaces/IGovernance.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IGovernance {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error ProposalHasNotExpired(uint256 proposalId);
    error ProposalExpired();
    error InsufficientNominations();
    error GCAContractAlreadySet();
    error CallerNotGCA();
    error CallerNotGCC();
    error CallerNotVetoCouncilMember();
    error ZeroAddressNotAllowed();
    error ContractsAlreadySet();
    error NominationCostGreaterThanAllowance();
    error ProposalDoesNotExist();
    error WeekNotStarted();
    error WeekNotFinalized();
    error InsufficientRatifyOrRejectVotes();
    error RatifyOrRejectPeriodEnded();
    error RatifyOrRejectPeriodNotEnded();
    error MostPopularProposalNotSelected();
    error ProposalAlreadyVetoed();
    error AlreadyEndorsedWeek();
    error OnlyGCAElectionsCanBeEndorsed();
    error MaxGCAEndorsementsReached();
    error VetoCouncilElectionsCannotBeVetoed();
    error GCACouncilElectionsCannotBeVetoed();
    error ProposalsMustBeExecutedSynchonously();
    error ProposalNotInitialized();
    error RFCPeriodNotEnded();
    error ProposalAlreadyExecuted();
    error ProposalIdDoesNotMatchMostPopularProposal();
    error ProposalNotMostPopular();
    error VetoCouncilProposalCreationOldMemberCannotEqualNewMember();
    error MaximumNumberOfGCAS();
    error InvalidSpendNominationsOnProposalSignature();

    error MaxSlashesInGCAElection();
    error SpendNominationsOnProposalSignatureExpired();
    error ProposalIsVetoed();
    error VetoMemberCannotBeNullAddress();
    error WeekMustHaveEndedToAcceptRatifyOrRejectVotes();

    /* -------------------------------------------------------------------------- */
    /*                                    enums                                   */
    /* -------------------------------------------------------------------------- */
    enum ProposalType {
        NONE, //default value for unset proposals
        VETO_COUNCIL_ELECTION_OR_SLASH,
        GCA_COUNCIL_ELECTION_OR_SLASH,
        GRANTS_PROPOSAL,
        CHANGE_GCA_REQUIREMENTS,
        REQUEST_FOR_COMMENT
    }

    enum ProposalStatus {
        NONE,
        EXECUTED_WITH_ERROR,
        EXECUTED_SUCCESSFULLY,
        VETOED
    }

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @param proposalType the type of the proposal
     * @param expirationTimestamp the timestamp at which the proposal expires
     * @param data the data of the proposal
     */
    struct Proposal {
        ProposalType proposalType;
        uint64 expirationTimestamp;
        uint184 votes;
        bytes data;
    }

    /* -------------------------------------------------------------------------- */
    /*                                   events                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Emitted when a Veto Council Election or Slash proposal is created
     * @param proposalId the id of the proposal
     * @param proposer the address of the proposer
     * @param oldAgent the address of the old agent
     * @param newAgent the address of the new agent
     * @param slashOldAgent whether or not to slash the old agent
     * @param nominationsUsed the amount of nominations used
     */
    event VetoCouncilElectionOrSlash(
        uint256 indexed proposalId,
        address indexed proposer,
        address oldAgent,
        address newAgent,
        bool slashOldAgent,
        uint256 nominationsUsed
    );

    /**
     * @notice Emitted when a GCA Council Election or Slash proposal is created
     * @param proposalId the id of the proposal
     * @param proposer the address of the proposer
     * @param agentsToSlash the addresses of the agents to slash
     * @param newGCAs the addresses of the new GCAs
     * @param proposalCreationTimestamp the timestamp at which the proposal was created
     *         -   This is necessary due to the proposalHashes logic in GCA
     * @param nominationsUsed the amount of nominations used
     */
    event GCACouncilElectionOrSlashCreation(
        uint256 indexed proposalId,
        address indexed proposer,
        address[] agentsToSlash,
        address[] newGCAs,
        uint256 proposalCreationTimestamp,
        uint256 nominationsUsed
    );

    /**
     * @notice emitted when a grants proposal is created
     * @param proposalId the id of the proposal
     * @param proposer the address of the proposer
     * @param recipient the address of the recipient
     * @param amount the amount of tokens to send
     * @param hash the hash of the proposal contents
     * @param nominationsUsed the amount of nominations used
     */
    event GrantsProposalCreation(
        uint256 indexed proposalId,
        address indexed proposer,
        address recipient,
        uint256 amount,
        bytes32 hash,
        uint256 nominationsUsed
    );

    /**
     * @notice emitted when a proposal to change the GCA requirements is created
     * @param proposalId the id of the proposal
     * @param proposer the address of the proposer
     * @param requirementsHash the hash of the requirements
     * @param nominationsUsed the amount of nominations used
     */
    event ChangeGCARequirementsProposalCreation(
        uint256 indexed proposalId, address indexed proposer, bytes32 requirementsHash, uint256 nominationsUsed
    );

    /**
     * @notice emitted when a request for comment is created
     * @param proposalId the id of the proposal
     * @param proposer the address of the proposer
     * @param rfcHash the hash of the requirements string
     * @param nominationsUsed the amount of nominations used
     */
    event RFCProposalCreation(
        uint256 indexed proposalId, address indexed proposer, bytes32 rfcHash, uint256 nominationsUsed
    );

    /**
     * @notice emitted when a long glow staker casts a ratify vote on a proposal
     * @param proposalId the id of the proposal
     * @param voter the address of the voter
     * @param numVotes the number of ratify votes
     */
    event RatifyCast(uint256 indexed proposalId, address indexed voter, uint256 numVotes);

    /**
     * @notice emitted when a long glow staker casts a reject vote on a proposal
     * @param proposalId the id of the proposal
     * @param voter the address of the voter
     * @param numVotes the number of reject votes
     */
    event RejectCast(uint256 indexed proposalId, address indexed voter, uint256 numVotes);

    /**
     * @notice emitted when nominations are used on a proposal
     * @param proposalId the id of the proposal
     * @param spender the address of the spender
     * @param amount the amount of nominations used
     */
    event NominationsUsedOnProposal(uint256 indexed proposalId, address indexed spender, uint256 amount);

    /**
     * @notice emitted when a proposal is set as the most popular proposal at a week
     * @param weekId - the weekId in which the proposal was selected as the most popular proposal
     * @param proposalId - the id of the proposal that was selected as the most popular proposal
     */
    event MostPopularProposalSet(uint256 indexed weekId, uint256 indexed proposalId);

    /**
     * @notice emitted when a proposal is ratified
     * @param weekId - the weekId in which the proposal to be vetoed was selected as the most popular proposal
     * @param vetoer - the address of the veto council member who vetoed the proposal
     * @param proposalId - the id of the proposal that was vetoed
     */
    event ProposalVetoed(uint256 indexed weekId, address indexed vetoer, uint256 proposalId);

    /**
     * @notice emitted when an rfc proposal is executed succesfully.
     * - RFC Proposals don't change the state of the system, so rather than performing state changes
     *         - we emit an event to alert that the proposal was executed succesfully
     *         - and that the rfc requires attention
     * @param proposalId - the id of the proposal from which the rfc was created
     * @param requirementsHash - the hash of the requirements string
     */
    event RFCProposalExecuted(uint256 indexed proposalId, bytes32 requirementsHash);

    /**
     * @notice emitted when a proposal is executed  for the week
     * @param week - the week for which the proposal was the most popular proposal
     * @param proposalId - the id of the proposal that was executed
     * @param proposalType - the type of the proposal that was executed
     * @param success - whether or not the proposal was executed succesfully
     */
    event ProposalExecution(uint256 indexed week, uint256 proposalId, ProposalType proposalType, bool success);

    /**
     * @notice Allows the GCC contract to grant nominations to {to} when they retire GCC
     * @param to the address to grant nominations to
     * @param amount the amount of nominations to grant
     */
    function grantNominations(address to, uint256 amount) external;

    /**
     * @notice Executes a most popular proposal at a given week
     * @dev a proposal that has not been ratified or rejected can be executed
     *         - but should never make any changes to the system (exceptions are detailed in the implementation)
     * @dev proposals that have met their requirements to perform state changes are executed as well
     * @dev no execution of any proposal should ever revert as this will freeze the governance contract
     * @param weekId the weekId that containst the 'mostPopularProposal' at that week
     * @dev proposals must be executed synchronously to ensure that the state of the system is consistent
     */
    function executeProposalAtWeek(uint256 weekId) external;

    /**
     * @notice syncs all proposals that must be synced
     */
    function syncProposals() external;

    /**
     * @notice allows a veto council member to endorse a gca election
     * @param weekId the weekId of the gca election to endorse
     */
    function endorseGCAProposal(uint256 weekId) external;
}

// File: src/CarbonCreditDescendingPriceAuction.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {HalfLifeCarbonCreditAuction} from "@/libraries/HalfLifeCarbonCreditAuction.sol";
import {ICarbonCreditAuction} from "@/interfaces/ICarbonCreditAuction.sol";
/**
 * @title CarbonCreditDescendingPriceAuction
 * @notice This contract is a reverse dutch auction for GCC.
 *         - The price has a half life of 1 week
 *         - The max that the price can grow is 2x per 24 hours
 *         - For every sale made, the price increases by the % of the total sold that the sale was
 *             - For example, if 10% of the available GCC is sold, then the price increases by 10%
 *             - If 100% of the available GCC is sold, then the price doubles
 *         - GCC is added to the pool of available GCC linearly over the course of a week
 *         - When new GCC is added, all pending vesting amounts and the new amount are vested over the course of a week
 *         - There is no cap on the amount of GCC that can be purchased in a single transaction
 *         - All GCC donations must be registered by the miner pool contract
 * @author DavidVorick
 * @author 0xSimon(twitter) -  0xSimbo(github)
 */

contract CarbonCreditDescendingPriceAuction is ICarbonCreditAuction {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error CallerNotGCC();
    error UserPriceNotHighEnough();
    error NotEnoughGCCForSale();
    error CannotBuyZeroUnits();

    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */

    /// @dev The precision (magnifier) used for calculations
    uint256 private constant PRECISION = 1e8;
    /// @dev The number of seconds in a day
    uint256 private constant ONE_DAY = uint256(1 days);
    /// @dev The number of seconds in a week
    uint256 private constant ONE_WEEK = uint256(7 days);
    /**
     * @notice the amount of GCC sold within a single unit (0.000000000001 GCC)
     * @dev This is equal to 1e-12 GCC
     */
    uint256 public constant SALE_UNIT = 1e6;

    /* -------------------------------------------------------------------------- */
    /*                                 immutables                                 */
    /* -------------------------------------------------------------------------- */

    /// @notice The GLOW token
    IERC20 public immutable GLOW;
    /// @notice The GCC token
    IERC20 public immutable GCC;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev a variable to keep track of the total amount of GCC that has been fully vested
     *         - it's not accurate and should only be used in conjunction with
     *             - {totalAmountReceived} to calculate the total supply
     *             - as shown in {totalSupply}
     */
    uint256 internal _pesudoTotalAmountFullyAvailableForSale;

    /// @notice The total amount of GLOW received from the miner pool
    uint256 public totalAmountReceived;

    /// @notice The total number of units of GCC sold
    uint256 public totalUnitsSold;

    /// @notice The price of GCC 24 hours ago
    ///         - this price is not accurate if there have been no sales in the last 24 hours
    ///         - it should not be relied on for accurate calculations
    uint256 public pseudoPrice24HoursAgo;

    /// @dev The price of GCC per sale unit
    /// @dev this price is not the actual price, and should be used in conjunction with {getPricePerUnit}
    uint256 internal pricePerSaleUnit;

    /// @notice The timestamps
    Timestamps public timestamps;

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev A struct to keep track of the timestamps all in a single slot
     * @param lastSaleTimestamp the timestamp of the last sale
     * @param lastReceivedTimestamp the timestamp of the last time GCC was received from the miner pool
     * @param lastPriceChangeTimestamp the timestamp of the last time the price changed
     */
    struct Timestamps {
        uint64 lastSaleTimestamp;
        uint64 lastReceivedTimestamp;
        uint64 lastPriceChangeTimestamp;
        uint64 firstReceivedTimestamp;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @param glow the GLOW token
     * @param gcc the GCC token
     * @param startingPrice the starting price of 1 unit of GCC
     */
    constructor(IERC20 glow, IERC20 gcc, uint256 startingPrice) payable {
        GLOW = glow;
        GCC = gcc;
        pricePerSaleUnit = startingPrice;
        pseudoPrice24HoursAgo = startingPrice;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 buy gcc                                    */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function buyGCC(uint256 unitsToBuy, uint256 maxPricePerUnit) external {
        if (unitsToBuy == 0) {
            _revert(CannotBuyZeroUnits.selector);
        }
        Timestamps memory _timestamps = timestamps;
        uint256 _lastPriceChangeTimestamp = _timestamps.lastPriceChangeTimestamp;
        uint256 _pseudoPrice24HoursAgo = pseudoPrice24HoursAgo;
        uint256 price = getPricePerUnit();
        if (price > maxPricePerUnit) {
            _revert(UserPriceNotHighEnough.selector);
        }
        uint256 gccPurchasing = unitsToBuy * SALE_UNIT;
        uint256 glowToTransfer = unitsToBuy * price;

        uint256 totalSaleUnitsAvailable = totalSaleUnits();
        uint256 saleUnitsLeftForSale = totalSaleUnitsAvailable - totalUnitsSold;

        if (saleUnitsLeftForSale < unitsToBuy) {
            _revert(NotEnoughGCCForSale.selector);
        }

        uint256 newPrice = price + (price * (unitsToBuy * PRECISION / saleUnitsLeftForSale) / PRECISION);

        //The new price can never grow more than 100% in 24 hours
        if (newPrice * PRECISION / _pseudoPrice24HoursAgo > 2 * PRECISION) {
            newPrice = _pseudoPrice24HoursAgo * 2;
        }
        //If it's been more than a day since the last sale, then update the price
        //To the price in the current tx
        //Also update the last price change timestamp
        if (block.timestamp - _lastPriceChangeTimestamp > ONE_DAY) {
            pseudoPrice24HoursAgo = price;
            _lastPriceChangeTimestamp = block.timestamp;
        }

        //
        pricePerSaleUnit = newPrice;

        totalUnitsSold += unitsToBuy;
        timestamps = Timestamps({
            lastSaleTimestamp: uint64(block.timestamp),
            lastReceivedTimestamp: _timestamps.lastReceivedTimestamp,
            lastPriceChangeTimestamp: uint64(_lastPriceChangeTimestamp),
            firstReceivedTimestamp: _timestamps.firstReceivedTimestamp
        });
        GLOW.transferFrom(msg.sender, address(this), glowToTransfer);
        GCC.transfer(msg.sender, gccPurchasing);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 receive gcc                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function receiveGCC(uint256 amount) external {
        if (msg.sender != address(GCC)) {
            _revert(CallerNotGCC.selector);
        }
        Timestamps memory _timestamps = timestamps;
        _pesudoTotalAmountFullyAvailableForSale = totalSupply();
        timestamps = Timestamps({
            lastSaleTimestamp: _timestamps.lastSaleTimestamp,
            lastReceivedTimestamp: uint64(block.timestamp),
            lastPriceChangeTimestamp: _timestamps.lastPriceChangeTimestamp,
            firstReceivedTimestamp: _timestamps.firstReceivedTimestamp == 0
                ? uint64(block.timestamp)
                : _timestamps.firstReceivedTimestamp
        });
        totalAmountReceived += amount;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function getPricePerUnit() public view returns (uint256) {
        Timestamps memory _timestamps = timestamps;
        uint256 _lastSaleTimestamp = _timestamps.lastSaleTimestamp;
        uint256 firstReceivedTimestamp = _timestamps.firstReceivedTimestamp;
        if (firstReceivedTimestamp == 0) {
            return pricePerSaleUnit;
        }
        if (_lastSaleTimestamp == 0) {
            _lastSaleTimestamp = firstReceivedTimestamp;
        }
        uint256 _pricePerSaleUnit = pricePerSaleUnit;
        return
            HalfLifeCarbonCreditAuction.calculateHalfLifeValue(_pricePerSaleUnit, block.timestamp - _lastSaleTimestamp);
    }

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function totalSupply() public view returns (uint256) {
        Timestamps memory _timestamps = timestamps;
        uint256 _lastReceivedTimestamp = _timestamps.lastReceivedTimestamp;
        uint256 _totalAmountReceived = totalAmountReceived;
        uint256 amountThatNeedsToVest = _totalAmountReceived - _pesudoTotalAmountFullyAvailableForSale;
        uint256 timeDiff = _min(ONE_WEEK, block.timestamp - _lastReceivedTimestamp);
        return (_pesudoTotalAmountFullyAvailableForSale + amountThatNeedsToVest * timeDiff / ONE_WEEK);
    }

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function unitsForSale() external view returns (uint256) {
        return totalSaleUnits() - totalUnitsSold;
    }

    /**
     * @inheritdoc ICarbonCreditAuction
     */
    function totalSaleUnits() public view returns (uint256) {
        return totalSupply() / (SALE_UNIT);
    }

    /* -------------------------------------------------------------------------- */
    /*                                     utils                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @param a the first number
     * @param b the second number
     * @return smaller - the smaller of the two numbers
     */
    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? b : a;
    }

    /**
     * @notice More efficiently reverts with a bytes4 selector
     * @param selector The selector to revert with
     */
    function _revert(bytes4 selector) private pure {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }
}

// File: src/interfaces/IUniswapRouterV2.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IUniswapRouterV2 {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function factory() external view returns (address);
}

// File: src/ImpactCatalyst.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IUniswapRouterV2} from "@/interfaces/IUniswapRouterV2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IUniswapV2Pair} from "@/interfaces/IUniswapV2Pair.sol";
import {UniswapV2Library} from "@/libraries/UniswapV2Library.sol";
/**
 * @title ImpactCatalyst
 * @notice A contract for managing the GCC and USDC commitment
 *         A commitment is when a user `donates` their GCC or USDC to the GCC-USDC pool
 *         to increase the liquidity of the pool and earn nominations
 *         For each commit, `amount` of GCC or USDC is swapped for the other token
 *         for the optimal amount such that the return amount of the other token
 *         is exactly enough to add liquidity to the GCC-USDC pool without any leftover of either token
 *         (precision errors may have small dust)
 *         - Nominations are granted as (sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition))
 *                 - or as the amount of liquidity tokens created from adding liquidity to the GCC-USDC pool
 *         - This is done to battle the quadratic nature of K in the UniswapV2Pair contract and standardize nominations
 * @dev only the GCC contract can call this contract since GCC is the only contract that is allowed to grant nominations
 * - having the catalyst calls be open would lead to commitment that would not earn any impact points / rewards / nominations
 */

contract ImpactCatalyst {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error CallerNotGCC();
    error PrecisionLossLeadToUnderflow();
    error NotEnoughImpactPowerFromCommitment();

    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /// @dev the magnification of GCC to use in {findOptimalAmountToSwap} to reduce precision loss
    /// @dev GCC is in 18 decimals, so we can make it 1e18 to reduce precision loss
    uint256 private constant GCC_MAGNIFICATION = 1e18;

    /// @dev the magnification of USDC to use in {findOptimalAmountToSwap} to reduce precision loss
    /// @dev USDC is in 6 decimals, so we can make it 1e24 to reduce precision loss
    uint256 private constant USDC_MAGNIFICATION = 1e24;

    /* -------------------------------------------------------------------------- */
    /*                                 immutables                                 */
    /* -------------------------------------------------------------------------- */

    /// @notice the GCC token
    address public immutable GCC;

    /// @notice the USDC token
    address public immutable USDC;

    /// @notice the uniswap router
    IUniswapRouterV2 public immutable UNISWAP_ROUTER;

    /// @notice the uniswap factory
    address public immutable UNISWAP_V2_FACTORY;

    /// @notice the uniswap pair of GCC and USDC
    address public immutable UNISWAP_V2_PAIR;

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @param _usdc - the address of the USDC token
     * @param router - the address of the uniswap router
     * @param factory - the address of the uniswap factory
     * @param pair - the address of the uniswap pair of GCC and USDC
     */
    constructor(address _usdc, address router, address factory, address pair) payable {
        GCC = msg.sender;
        USDC = _usdc;
        UNISWAP_ROUTER = IUniswapRouterV2(router);
        UNISWAP_V2_FACTORY = factory;
        UNISWAP_V2_PAIR = pair;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 gcc commits                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice entry point for GCC to commit GCC
     * @dev the commit process is as follows:
     *         1. GCC is swapped for USDC
     *         2. GCC and USDC are added to the GCC-USDC pool
     *         3. The user receives impact points and nominations (handled in GCC contract)
     *     - The point is to commit the GCC while adding liquidity to increase incentives for farms
     * @param amount the amount of GCC to commit
     * @param minImpactPower the minimum amount of impact power expected to be earned from the commitment
     * @return usdcEffect - the amount of USDC used in the LP Position
     * @return nominations - the amount of nominations to earn sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition)
     *                        - we do this to battle the quadratic nature of K in the UniswapV2Pair contract and standardize nominations
     */
    function commitGCC(uint256 amount, uint256 minImpactPower)
        external
        returns (uint256 usdcEffect, uint256 nominations)
    {
        // Commitments can only be made through the GCC contract
        if (msg.sender != GCC) {
            _revert(CallerNotGCC.selector);
        }
        // Find the reserves of GCC and USDC in the GCC-USDC pool
        (uint256 reserveA, uint256 reserveB,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();
        //Find the reserve of GCC and USDC in the GCC-USDC pool
        uint256 reserveGCC = GCC < USDC ? reserveA : reserveB;

        // Find the optimal amount of GCC to swap for USDC
        // This ensures that the return amount of USDC after the swap
        // Should be exactly enough to add liquidity to the GCC-USDC pool with the remainder of `amount` of GCC left over
        uint256 amountToSwap =
            findOptimalAmountToSwap(amount * GCC_MAGNIFICATION, reserveGCC * GCC_MAGNIFICATION) / GCC_MAGNIFICATION;

        //Approve the GCC token to be spent by the router
        IERC20(GCC).approve(address(UNISWAP_ROUTER), amount);
        //Create the path for the swap
        address[] memory path = new address[](2);
        path[0] = GCC;
        path[1] = USDC;
        //Swap the GCC for USDC

        // If impact power = sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition)
        // square both sides, and we get impact power ^ 2 = amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition
        // so we can find the minimum amount of USDC expected from the swap by doing
        // minimumUSDCExpected = (minImpactPower * minImpactPower) / (amount - amountToSwap)
        // since amount - amountToSwap is the expected amount of GCC used in the liquidity position
        uint256 minimumUSDCExpected = (minImpactPower * minImpactPower) / (amount - amountToSwap);
        uint256[] memory amounts = UNISWAP_ROUTER.swapExactTokensForTokens({
            amountIn: amountToSwap,
            // we allow for a 1% slippage based on the minimum impact power,
            // due to potential rounding errors in the findOptimalAmountToSwap function
            amountOutMin: minimumUSDCExpected * 99 / 100,
            path: path,
            to: address(this),
            deadline: block.timestamp
        });

        //Find how much USDC was received from the swap
        uint256 amountUSDCReceived = amounts[1];
        //Approve the USDC token to be spent by the router
        IERC20(USDC).approve(address(UNISWAP_ROUTER), amountUSDCReceived);
        uint256 amountToAddInLiquidity = amount - amounts[0];

        // Add liquidity to the GCC-USDC pool
        // Note: There could be a tax due to USDC Upgrades, and there could also be ERC777 type upgrades,
        // When glow relaunches after the guarded launch, this will be accounted for
        (uint256 actualAmountGCCUsedInLP, uint256 actualAmountUSDCUsedInLP,) = UNISWAP_ROUTER.addLiquidity({
            tokenA: GCC,
            tokenB: USDC,
            amountADesired: amountToAddInLiquidity,
            amountBDesired: amountUSDCReceived,
            // we allow for a 1% slippage due to potential rounding errors
            // This seems high, but it's simply a precaution to prevent the transaction from reverting
            // The bulk of the calculation happens in the logic above
            amountAMin: amountToAddInLiquidity * 99 / 100,
            amountBMin: amountUSDCReceived * 99 / 100,
            to: address(this),
            deadline: block.timestamp
        });

        uint256 actualImpactPowerEarned = sqrt(actualAmountGCCUsedInLP * actualAmountUSDCUsedInLP);
        usdcEffect = actualAmountUSDCUsedInLP;
        if (actualImpactPowerEarned < minImpactPower) {
            _revert(NotEnoughImpactPowerFromCommitment.selector);
        }

        // Set usdcEffect to the amount of USDC used in the liquidity position
        // set the nominations to sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition)
        nominations = actualImpactPowerEarned;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 usdc commits                               */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice entry point for GCC to commit USDC
     * @dev the commit process is as follows:
     *         1. USDC is swapped for GCC
     *         2. GCC and USDC are added to the GCC-USDC pool
     *         3. The user receives impact points and nominations (handled in GCC contract)
     * @param amount the amount of USDC to commit
     * @param minImpactPower the minimum amount of impact power expected to be earned from the commitment
     * @return nominations - the amount of nominations to earn sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition)
     *                        - we do this to battle the quadratic nature of K in the UniswapV2Pair contract and standardize nominations
     */
    function commitUSDC(uint256 amount, uint256 minImpactPower) external returns (uint256 nominations) {
        // Commitments can only be made through the GCC contract
        if (msg.sender != GCC) {
            _revert(CallerNotGCC.selector);
        }
        // Find the reserves of GCC and USDC in the GCC-USDC pool
        (uint256 reserveA, uint256 reserveB,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();
        // Find the reserve of GCC and USDC in the GCC-USDC pool
        uint256 reserveUSDC = USDC < GCC ? reserveA : reserveB;
        // Find the optimal amount of USDC to swap for GCC
        // This ensures that the the return amount of GCC after the swap
        // Should be exactly enough to add liquidity to the GCC-USDC pool with the remainder of `amount`  USDC left over
        uint256 optimalSwapAmount =
            findOptimalAmountToSwap(amount * USDC_MAGNIFICATION, reserveUSDC * USDC_MAGNIFICATION) / USDC_MAGNIFICATION;

        //Approve the USDC token to be spent by the router
        IERC20(USDC).approve(address(UNISWAP_ROUTER), amount);
        //Create the path for the swap
        address[] memory path = new address[](2);
        path[0] = USDC;
        path[1] = GCC;

        // If impact power = sqrt(amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition)
        // square both sides, and we get impact power ^ 2 = amountGCCUsedInLiquidityPosition * amountUSDCUsedInLiquidityPosition
        // so we can find the minimum amount of GCC expected from the swap by doing
        // minimumGCCExpected = (minImpactPower * minImpactPower) / (amount - optimalSwapAmount)
        // since amount - optimalSwapAmount is the expected amount of USDC used in the liquidity position
        uint256 minimumGCCExpected = (minImpactPower * minImpactPower) / (amount - optimalSwapAmount);

        // Swap the USDC for GCC
        uint256[] memory amounts = UNISWAP_ROUTER.swapExactTokensForTokens({
            amountIn: optimalSwapAmount,
            // we allow for a 1% slippage based on the minimum impact power,
            // due to potential rounding errors in the findOptimalAmountToSwap function
            amountOutMin: minimumGCCExpected * 99 / 100,
            path: path,
            to: address(this),
            deadline: block.timestamp
        });
        // Approve the GCC token to be spent by the router
        IERC20(GCC).approve(address(UNISWAP_ROUTER), amounts[1]);

        uint256 amountToAddInLiquidity = amount - amounts[0];

        // Add liquidity to the GCC-USDC pool
        // Note: There could be a tax due to USDC Upgrades, and there could also be ERC777 type upgrades,
        // When glow relaunches after the guarded launch, this will be accounted for
        (uint256 actualAmountUSDCUsedInLP, uint256 actualAmountGCCUsedInLP,) = UNISWAP_ROUTER.addLiquidity({
            tokenA: USDC,
            tokenB: GCC,
            amountADesired: amountToAddInLiquidity,
            amountBDesired: amounts[1],
            // we allow for a 1% slippage due to potential rounding errors
            // This seems high, but it's simply a precaution to prevent the transaction from reverting
            // The bulk of the calculation happens in the logic above
            amountAMin: amountToAddInLiquidity * 99 / 100,
            amountBMin: amounts[1] * 99 / 100,
            to: address(this),
            deadline: block.timestamp
        });

        uint256 actualImpactPowerEarned = sqrt(actualAmountGCCUsedInLP * actualAmountUSDCUsedInLP);
        if (actualImpactPowerEarned < minImpactPower) {
            _revert(NotEnoughImpactPowerFromCommitment.selector);
        }

        nominations = actualImpactPowerEarned;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice a helper function to estimate the impact power expected from a GCC commit
     * @dev there may be a slight difference between the actual impact power earned and the estimated impact power
     *     - A max .5% divergence should be accounted for when using this function
     * @param amount the amount of GCC to commit
     * @return expectedImpactPower - the amount of impact power expected to be earned from the commitment
     */
    function estimateUSDCCommitImpactPower(uint256 amount) external view returns (uint256 expectedImpactPower) {
        uint256 expectedImpactPower = _estimateUSDCCommitImpactPower(amount);
        return expectedImpactPower;
    }

    /**
     * @notice a helper function to estimate the impact power expected from a USDC commit
     * @dev there may be a slight difference between the actual impact power earned and the estimated impact power
     *     - A max .5% divergence should be accounted for when using this function
     * @param amount the amount of USDC to commit
     * @return expectedImpactPower - the amount of impact power expected to be earned from the commitment
     */
    function estimateGCCCommitImpactPower(uint256 amount) external view returns (uint256 expectedImpactPower) {
        uint256 expectedImpactPower = _estimateGCCCommitImpactPower(amount);
        return expectedImpactPower;
    }

    /**
     * @notice helper function to find the optimal amount of tokens to swap
     * @param amountTocommit the amount of tokens to commit
     * @param totalReservesOfToken the total reserves of the token to commit
     * @return optimalAmount - the optimal amount of tokens to swap
     */
    function findOptimalAmountToSwap(uint256 amountTocommit, uint256 totalReservesOfToken)
        public
        view
        returns (uint256)
    {
        uint256 a = sqrt(totalReservesOfToken) + 1; //adjust for div round down errors
        uint256 b = sqrt(3988000 * amountTocommit + 3988009 * totalReservesOfToken);
        uint256 c = 1997 * totalReservesOfToken;
        uint256 d = 1994;
        if (c > a * b) _revert(PrecisionLossLeadToUnderflow.selector); // prevent underflow
        uint256 res = ((a * b) - c) / d;
        return res;
    }

    /* -------------------------------------------------------------------------- */
    /*                               internal view funcs                          */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns {optimalSwapAmount, amountToAddInLiquidity, impactPowerExpected} for an USDC commit
     * @param amount the amount of USDC to commit
     * @dev there may be a slight difference between the actual impact power earned and the estimated impact power
     *     - A max .5% divergence should be accounted for when using this function
     * @return impactPowerExpected - the amount of impact power expected to be earned from the commitment
     */
    function _estimateUSDCCommitImpactPower(uint256 amount) internal view returns (uint256 impactPowerExpected) {
        // Get the reserves of GCC and USDC in the GCC-USDC pool
        (uint256 reserveA, uint256 reserveB,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();
        // Get GCC Reserve
        uint256 reserveGCC = GCC < USDC ? reserveA : reserveB;
        // Get USDC Reserve
        uint256 reserveUSDC = USDC < GCC ? reserveA : reserveB;

        // Calculate the optimal amount of USDC to swap for GCC
        uint256 optimalSwapAmount =
            findOptimalAmountToSwap(amount * USDC_MAGNIFICATION, reserveUSDC * USDC_MAGNIFICATION) / USDC_MAGNIFICATION;

        // Since we commit USDC, we want to simulate how much GCC we would get from the swap
        // This is also the same amount of GCC that will be used to add liquidity to the GCC-USDC pool
        uint256 gccEstimate = UniswapV2Library.getAmountOut(optimalSwapAmount, reserveUSDC, reserveGCC);

        // This is the amount of USDC to add in the LP, which is the amount-optimalSwapAmount
        // This number represents the balance of USDC after the swap
        uint256 amountUSDCToAddInLiquidity = amount - optimalSwapAmount;

        // The new reserves of GCC and USDC after the swap
        // We add the optimalSwapAmount to USDC, since we used it to swap for GCC
        // and, we subtract the gccEstimate from GCC, since it was used when we swapped our USDC
        uint256 reserveUSDC_afterSwap = reserveUSDC + optimalSwapAmount;
        uint256 reserveGCC_afterSwap = reserveGCC - gccEstimate;

        uint256 amountGCCOptimal =
            UniswapV2Library.quote(amountUSDCToAddInLiquidity, reserveUSDC_afterSwap, reserveGCC_afterSwap);

        if (amountGCCOptimal <= gccEstimate) {
            return sqrt(amountGCCOptimal * amountUSDCToAddInLiquidity);
        } else {
            uint256 amountUSDCOptimal = UniswapV2Library.quote(gccEstimate, reserveGCC_afterSwap, reserveUSDC_afterSwap);
            return sqrt(gccEstimate * amountUSDCOptimal);
        }
    }

    /**
     * @notice returns {optimalSwapAmount, amountToAddInLiquidity, impactPowerExpected} for a GCC commit
     * @param amount the amount of GCC to commit
     * @dev there may be a slight difference between the actual impact power earned and the estimated impact power
     *     - A max .5% divergence should be accounted for when using this function
     * @return impactPowerExpected - the amount of impact power expected to be earned from the commitment
     */
    function _estimateGCCCommitImpactPower(uint256 amount) internal view returns (uint256 impactPowerExpected) {
        //Get the reserves of GCC and USDC in the GCC-USDC pool
        (uint256 reserveA, uint256 reserveB,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();

        // Get GCC Reserve
        uint256 reserveGCC = GCC < USDC ? reserveA : reserveB;
        // Get USDC Reserve
        uint256 reserveUSDC = USDC < GCC ? reserveA : reserveB;

        // Calculate the optimal amount of GCC to swap for USDC
        uint256 optimalSwapAmount =
            findOptimalAmountToSwap(amount * GCC_MAGNIFICATION, reserveGCC * GCC_MAGNIFICATION) / GCC_MAGNIFICATION;

        // Since we commit GCC, we want to simulate how much USDC we would get from the swap
        uint256 usdcEstimate = UniswapV2Library.getAmountOut(optimalSwapAmount, reserveGCC, reserveUSDC);

        //This is the amount of GCC to add in the LP, which is the amount-optimalSwapAmount
        uint256 amountGCCToAddInLiquidity = amount - optimalSwapAmount;

        // The new reserves of GCC and USDC after the swap
        // We add the optimalSwapAmount to GCC reserves, since we used it to swap for USDC
        // and, we subtract the usdcEstimate from USDC reserves, since it was used when we swapped our GCC
        uint256 reserveGCC_afterSwap = reserveGCC + optimalSwapAmount;
        uint256 reserveUSDC_afterSwap = reserveUSDC - usdcEstimate;

        uint256 amountUSDCOptimal =
            UniswapV2Library.quote(amountGCCToAddInLiquidity, reserveGCC_afterSwap, reserveUSDC_afterSwap);

        if (amountUSDCOptimal <= usdcEstimate) {
            impactPowerExpected = sqrt(amountGCCToAddInLiquidity * amountUSDCOptimal);
            return impactPowerExpected;
        } else {
            uint256 amountGCCOptimal = UniswapV2Library.quote(usdcEstimate, reserveUSDC_afterSwap, reserveGCC_afterSwap);
            impactPowerExpected = sqrt(usdcEstimate * amountGCCOptimal);
            return impactPowerExpected;
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                                    utils                                   */
    /* -------------------------------------------------------------------------- */
    /// @dev forked from solady library
    /// @param x - the number to calculate the square root of
    /// @return z - the square root of x
    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    /**
     * @notice returns the minimum of two numbers
     * @param a - the first number
     * @param b - the second number
     * @return the minimum of a and b
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @notice More efficiently reverts with a bytes4 selector
     * @param selector The selector to revert with
     */

    function _revert(bytes4 selector) private pure {
        // solhint-disable-next-line no-inline-assembly
        assembly ("memory-safe") {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }
}

// File: src/interfaces/IERC20Permit.sol



pragma solidity ^0.8.11;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IERC20Permit is IERC20 {
    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
        external;
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)

pragma solidity ^0.8.20;

import {ERC20} from "../ERC20.sol";
import {Context} from "../../../utils/Context.sol";

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20Burnable is Context, ERC20 {
    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 value) public virtual {
        _burn(_msgSender(), value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, deducting from
     * the caller's allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `value`.
     */
    function burnFrom(address account, uint256 value) public virtual {
        _spendAllowance(account, _msgSender(), value);
        _burn(account, value);
    }
}

// File: src/MinerPoolAndGCA/GCA.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IGCA} from "@/interfaces/IGCA.sol";
import {IGlow} from "@/interfaces/IGlow.sol";
import {GCASalaryHelper} from "./GCASalaryHelper.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {_BUCKET_DURATION} from "@/Constants/Constants.sol";

/**
 * @title GCA (Glow Certification Agent)
 * @author @DavidVorick
 * @author @0xSimon(twitter) - 0xSimon(github)
 *  @notice this contract is the entry point for GCAs to submit reports and claim payouts
 *  @notice GCA's submit weekly reports that contain how many carbon credits have been created
 *             - and which farms should get rewarded for the creation of those credits
 * @notice The weekly reports that GCA's submit into are called `buckets`
 * @notice Each `bucket` has a 1 week period for report submission
 *             - followed by a 1 week period before its finalized
 *             - during this finalization period, the veto council can decide to delay the bucket by 90 days
 *             - should they find anything suspicious in the bucket.
 *                - A delayed bucket should always finalize 90 days after the delay event
 *                - This should give governance enough time to slash the GCA that submitted the faulty report
 *                - This slash event causes all buckets that were not finalized at the time of the slash, to be permanently slashed
 *                - The exception is that the current GCA's have 1-2 weeks after the slash to reinstate the bucket
 *                - Reinstating the buckets deletes all the past reports and allows the GCAs to submit fresh reports
 *             - after the bucket has passed this finalization period, the bucket's rewards become available for distribution to solar farms,
 *                and the GCC created is minted and sent to the Carbon Credit Auction
 *             - These actions above take place in the `MinerPoolAndGCA` contract
 * @notice Governance has the ability to change and slash the GCA's.
 *
 */
contract GCA is IGCA, GCASalaryHelper {
    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /// @dev the return value if an index position is not found in an array
    uint256 private constant _INDEX_NOT_FOUND = type(uint256).max;

    /// @notice the shift to apply to the bitpacked compensation plans
    uint256 private constant _UINT24_SHIFT = 24;

    /// @notice the mask to apply to the bitpacked compensation plans
    uint256 private constant _UINT24_MASK = 0xFFFFFF;

    /// @dev 200 Billion in 18 decimals
    uint256 private constant _200_BILLION = 200_000_000_000 ether;

    /// @dev the max uint64 divided by 5
    /// @dev this is used to check if the total weight of a report is less than the max uint64 / 5
    /// @dev the max sum of all weights is type(uint64).max, so we can not allow an overflow by a bad
    uint256 private constant _UINT64_MAX_DIV5 = type(uint64).max / 5;

    /// @dev mask to apply a uint128 mask to a uint256
    /// @dev this is used to get the `finalizationTimestamp` from the `Bucket` struct
    ///     - which is a uint128 stored in the last 128 bits of the uint256
    uint256 internal constant _UINT128_MASK = (1 << 128) - 1;

    /// @dev mask to apply a uint64 mask to a uint256
    /// @dev this is used to get the `originalNonce` and `lastUpdatedNonce` from the `Bucket` struct
    /// -  `originalNonce` is a uint64 stored in the first 64 bits of the uint256
    /// -  `lastUpdatedNonce` is a uint64 stored in the second 64 bits of the uint256
    uint256 internal constant _UINT64_MASK = (1 << 64) - 1;

    /* -------------------------------------------------------------------------- */
    /*                                 immutables                                 */
    /* -------------------------------------------------------------------------- */
    /// @notice the address of the glow token
    IGlow public immutable GLOW_TOKEN;

    /// @notice the address of the governance contract
    address public immutable GOVERNANCE;

    /// @notice the timestamp of the genesis block
    uint256 public immutable GENESIS_TIMESTAMP;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */
    /// @notice the index of the last proposal that was updated + 1
    uint256 public nextProposalIndexToUpdate;

    /// @notice the hashes of the proposals that have been submitted from {GOVERNANCE}
    bytes32[] public proposalHashes;

    /// @notice the addresses of the gca agents
    address[] public gcaAgents;

    /**
     * @notice the requirements hash of GCA Agents
     */
    bytes32 public requirementsHash;

    /**
     * @notice the current slash nonce
     */
    uint256 public slashNonce;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the timestamp of the slash event as [nonce]
     * @dev nonce -> slash timestamp
     */
    mapping(uint256 => uint256) public slashNonceToSlashTimestamp;

    /// @notice the gca payouts
    mapping(address => IGCA.GCAPayout) private _gcaPayouts;

    /// @notice bucket -> Bucket Struct
    mapping(uint256 => IGCA.Bucket) internal _buckets;

    /// @notice bucket -> Global State
    mapping(uint256 => IGCA.BucketGlobalState) internal _bucketGlobalState;

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice constructs a new GCA contract
     * @param _gcaAgents the addresses of the gca agents the contract starts with
     * @param _glowToken the address of the glow token
     * @param _governance the address of the governance contract
     * @param _requirementsHash the requirements hash of GCA Agents
     */
    constructor(address[] memory _gcaAgents, address _glowToken, address _governance, bytes32 _requirementsHash)
        payable
        GCASalaryHelper(_gcaAgents)
    {
        //Set the glow token
        GLOW_TOKEN = IGlow(_glowToken);
        //Set governance
        GOVERNANCE = _governance;
        //Set the GCA's
        _setGCAs(_gcaAgents);
        //Set the genesis timestamp
        GENESIS_TIMESTAMP = GLOW_TOKEN.GENESIS_TIMESTAMP();
        //Initialize the payouts for the gcas
        for (uint256 i; i < _gcaAgents.length; ++i) {
            _gcaPayouts[_gcaAgents[i]].lastClaimedTimestamp = uint64(GENESIS_TIMESTAMP);
        }
        //Set the GCA requirements hash
        requirementsHash = _requirementsHash;
        GCASalaryHelper.setZeroPaymentStartTimestamp();
    }

    /* -------------------------------------------------------------------------- */
    /*                              submit comp plans                             */
    /* -------------------------------------------------------------------------- */
    /// @inheritdoc IGCA
    function submitCompensationPlan(uint32[5] calldata plan, uint256 indexOfGCA) external {
        _revertIfFrozen();
        uint256 gcaLength = gcaAgents.length;
        if (msg.sender != gcaAgents[indexOfGCA]) _revert(IGCA.CallerNotGCAAtIndex.selector);
        GCASalaryHelper.handleCompensationPlanSubmission(plan, indexOfGCA, gcaLength);
        emit IGCA.CompensationPlanSubmitted(msg.sender, plan);
    }

    /* -------------------------------------------------------------------------- */
    /*                              submitting reports                            */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice allows GCAs to submit a weekly report and emit {data}
     *         - {data} is a bytes array that can be used to emit any data
     *         - it could contain the merkle tree, or any other data
     *         - it is not strictly enforced and GCA's should communicate what they are emitting
     * @param bucketId - the id of the bucket
     * @param totalNewGCC - the total amount of GCC to be created from the report
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight in the report
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight in the report
     * @param root - the merkle root containing all the reports (leaves) for the period
     */
    function submitWeeklyReport(
        uint256 bucketId,
        uint256 totalNewGCC,
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        bytes32 root
    ) external {
        _submitWeeklyReport(bucketId, totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, root);
        emit IGCA.BucketSubmissionEvent(
            bucketId, msg.sender, slashNonce, totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, root, ""
        );
    }

    /**
     * @notice allows GCAs to submit a weekly report and emit {data}
     *         - {data} is a bytes array that can be used to emit any data
     *         - it could contain the merkle tree, or any other data
     *         - it is not strictly enforced and GCA's should communicate what they are emitting
     * @param bucketId - the id of the bucket
     * @param totalNewGCC - the total amount of GCC to be created from the report
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight in the report
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight in the report
     * @param root - the merkle root containing all the reports (leaves) for the period
     * @param data - the data to emit
     */
    function submitWeeklyReportWithBytes(
        uint256 bucketId,
        uint256 totalNewGCC,
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        bytes32 root,
        bytes calldata data
    ) external {
        _submitWeeklyReport(bucketId, totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, root);
        emit IGCA.BucketSubmissionEvent(
            bucketId, msg.sender, slashNonce, totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, root, data
        );
    }

    /* -------------------------------------------------------------------------- */
    /*                              governance interaction                        */
    /* -------------------------------------------------------------------------- */
    /**
     * @inheritdoc IGCA
     */
    function setRequirementsHash(bytes32 _requirementsHash) external {
        if (msg.sender != GOVERNANCE) _revert(IGCA.CallerNotGovernance.selector);
        requirementsHash = _requirementsHash;
        emit IGCA.RequirementsHashUpdated(_requirementsHash);
    }

    /**
     * @inheritdoc IGCA
     */
    function pushHash(bytes32 hash, bool incrementSlashNonce) external {
        if (msg.sender != GOVERNANCE) _revert(IGCA.CallerNotGovernance.selector);
        if (incrementSlashNonce) {
            ++slashNonce;
        }
        proposalHashes.push(hash);
        emit IGCA.ProposalHashPushed(hash);
    }

    /**
     * @notice allows anyone to call this function to ensure that governance proposals are being taken into effect
     * @param gcasToSlash - the gca agents to slash
     * @param newGCAs - the new gca agents
     * @dev - this is a standalone function that anyone can call to ensure that
     *             - users dont pay too much gas when syncing proposals.
     * @dev if there is a hash to execute against, the contract will be frozen
     *             - if there is no hash to execute against, the contract will be available
     *             - to execute actions
     */
    function executeAgainstHash(
        address[] calldata gcasToSlash,
        address[] calldata newGCAs,
        uint256 proposalCreationTimestamp
    ) external {
        uint256 _nextProposalIndexToUpdate = nextProposalIndexToUpdate;
        uint256 len = proposalHashes.length;
        if (len == 0) _revert(IGCA.ProposalHashesEmpty.selector);
        bytes32 derivedHash = keccak256(abi.encode(gcasToSlash, newGCAs, proposalCreationTimestamp));
        //Slash nonce already get's incremented so we need to subtract 1
        if (gcasToSlash.length > 0) {
            slashNonceToSlashTimestamp[slashNonce - 1] = proposalCreationTimestamp;
        }
        if (proposalHashes[_nextProposalIndexToUpdate] != derivedHash) {
            _revert(IGCA.ProposalHashDoesNotMatch.selector);
        }

        GCASalaryHelper.callbackInElectionEvent(newGCAs);
        _setGCAs(newGCAs);
        _slashGCAs(gcasToSlash);
        nextProposalIndexToUpdate = _nextProposalIndexToUpdate + 1;
        emit IGCA.ProposalHashUpdate(_nextProposalIndexToUpdate, derivedHash);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 glow inflation                             */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice - an open function to claim the glow from inflation
     */
    function claimGlowFromInflation() public virtual {
        _claimGlowFromInflation();
    }

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */
    /// @inheritdoc IGCA
    function isGCA(address account, uint256 index) public view returns (bool) {
        if (_isFrozen()) return false;
        return gcaAgents[index] == account;
    }

    /// @inheritdoc IGCA
    function isGCA(address account) public view returns (bool) {
        if (_isFrozen()) return false;
        uint256 len = gcaAgents.length;
        unchecked {
            for (uint256 i; i < len; ++i) {
                if (gcaAgents[i] == account) return true;
            }
        }
        return false;
    }

    /// @inheritdoc IGCA
    function allGcas() public view returns (address[] memory) {
        return gcaAgents;
    }

    /// @inheritdoc IGCA
    function gcaPayoutData(address gca) public view returns (IGCA.GCAPayout memory) {
        return _gcaPayouts[gca];
    }

    /**
     * @inheritdoc IGCA
     */
    function getProposalHashes() external view returns (bytes32[] memory) {
        return proposalHashes;
    }

    /**
     * @inheritdoc IGCA
     */
    function getProposalHashes(uint256 start, uint256 end) external view returns (bytes32[] memory) {
        if (end > proposalHashes.length) end = proposalHashes.length;
        if (start > end) return new bytes32[](0);
        bytes32[] memory result = new bytes32[](end - start);
        unchecked {
            for (uint256 i = start; i < end; ++i) {
                result[i - start] = proposalHashes[i];
            }
        }
        return result;
    }

    /**
     * @inheritdoc IGCA
     */
    function bucketGlobalState(uint256 bucketId) external view returns (IGCA.BucketGlobalState memory) {
        return _bucketGlobalState[bucketId];
    }

    /**
     * @notice returns the start submission timestamp of a bucket
     * @param bucketId - the id of the bucket
     * @return the start submission timestamp of a bucket
     * @dev should not be used for reinstated buckets or buckets that need to be reinstated
     */
    function bucketStartSubmissionTimestampNotReinstated(uint256 bucketId) public view returns (uint128) {
        return SafeCast.toUint128(bucketId * bucketDuration() + GENESIS_TIMESTAMP);
    }

    /**
     * @notice returns the end submission timestamp of a bucket
     *         - GCA's wont be able to submit if block.timestamp >= endSubmissionTimestamp
     * @param bucketId - the id of the bucket
     * @return the end submission timestamp of a bucket
     * @dev should not be used for reinstated buckets or buckets that need to be reinstated
     */
    function bucketEndSubmissionTimestampNotReinstated(uint256 bucketId) public view returns (uint128) {
        return SafeCast.toUint128(bucketStartSubmissionTimestampNotReinstated(bucketId) + bucketDuration());
    }

    /**
     * @notice returns the finalization timestamp of a bucket
     * @param bucketId - the id of the bucket
     * @return the finalization timestamp of a bucket
     * @dev should not be used for reinstated buckets or buckets that need to be reinstated
     */
    function bucketFinalizationTimestampNotReinstated(uint256 bucketId) public view returns (uint128) {
        return SafeCast.toUint128(bucketEndSubmissionTimestampNotReinstated(bucketId) + bucketDuration());
    }

    /**
     * @inheritdoc IGCA
     */
    function bucket(uint256 bucketId) public view returns (IGCA.Bucket memory bucket) {
        return _buckets[bucketId];
    }

    /**
     * @inheritdoc IGCA
     */
    function isBucketFinalized(uint256 bucketId) public view returns (bool) {
        uint256 packedData;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, bucketId)
            mstore(0x20, _buckets.slot)
            let slot := keccak256(0x0, 0x40)
            // nonce, reinstated and finalizationTimestamp are all in the first slot
            packedData := sload(slot)
        }

        uint256 bucketLastUpdatedNonce = (packedData >> 64) & _UINT64_MASK;
        //First bit.
        //first 64 bits are originalNonce, next 64 bits are lastUpdatedNonce, last 128 bits are finalizationTimestamp
        //no need to us to use a mask since finalizationTimestamp takes up the last 128 bits
        uint256 finalizationTimestamp = packedData >> 128;

        uint256 _slashNonce = slashNonce;
        return _isBucketFinalized(bucketLastUpdatedNonce, finalizationTimestamp, _slashNonce);
    }

    /* -------------------------------------------------------------------------- */
    /*                                   internal                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice allows GCAs to submit a weekly report and emit {data}
     *         - {data} is a bytes array that can be used to emit any data
     *         - it could contain the merkle tree, or any other data
     *         - it is not strictly enforced and GCA's should communicate what they are emitting
     * @param bucketId - the id of the bucket
     * @param totalNewGCC - the total amount of GCC to be created from the report
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight in the report
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight in the report
     * @param root - the merkle root containing all the reports (leaves) for the period
     */

    function _submitWeeklyReport(
        uint256 bucketId,
        uint256 totalNewGCC,
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        bytes32 root
    ) internal {
        //GCAs can't submit if the contract is frozen (pending a proposal hash update)
        _revertIfFrozen();
        if (!isGCA(msg.sender)) _revert(NotGCA.selector);
        checkBucketSubmissionArithmeticInputs(totalGlwRewardsWeight, totalGRCRewardsWeight, totalNewGCC);
        //Need to check if bucket is slashed
        Bucket storage bucket = _buckets[bucketId];
        //Cache values
        uint256 len = bucket.reports.length;
        {
            uint256 bucketFinalizationTimestamp = bucket.finalizationTimestamp;

            uint256 lastUpdatedNonce = bucket.lastUpdatedNonce;
            //Get the submission start itimestamp
            uint256 bucketSubmissionStartTimestamp = bucketStartSubmissionTimestampNotReinstated(bucketId);
            if (block.timestamp < bucketSubmissionStartTimestamp) _revert(IGCA.BucketSubmissionNotOpen.selector);

            //Keep in mind, all bucketNonces start with 0
            //So on the first init, we need to set the bucketNonce to the slashNonce in storage
            {
                uint256 _slashNonce = slashNonce;
                //If not inititialized, intitialize the bucket
                if (bucketFinalizationTimestamp == 0) {
                    bucket.originalNonce = SafeCast.toUint64(_slashNonce);
                    bucket.lastUpdatedNonce = SafeCast.toUint64(_slashNonce);
                    bucket.finalizationTimestamp =
                        SafeCast.toUint128(bucketFinalizationTimestampNotReinstated(bucketId));
                    lastUpdatedNonce = _slashNonce;
                }

                {
                    /**
                     * If the bucket needs to be reinstated
                     *             we need to update the bucket accordingly
                     *             and we need to change the finalization timestamp
                     *             lastly, we need to delete all reports in storage if there are any
                     */
                    uint256 bucketSubmissionEndTimestamp = _calculateBucketSubmissionEndTimestamp(
                        bucketId, bucket.originalNonce, lastUpdatedNonce, _slashNonce, bucketFinalizationTimestamp
                    );
                    if (block.timestamp >= bucketSubmissionEndTimestamp) _revert(IGCA.BucketSubmissionEnded.selector);

                    if (lastUpdatedNonce != _slashNonce) {
                        bucket.lastUpdatedNonce = SafeCast.toUint64(_slashNonce);
                        //Need to check before storing the finalization timestamp in case
                        //the bucket was delayed.
                        if (bucketSubmissionEndTimestamp + bucketDuration() > bucketFinalizationTimestamp) {
                            bucket.finalizationTimestamp =
                                SafeCast.toUint128(bucketSubmissionEndTimestamp + bucketDuration());
                        }
                        //conditionally delete all reports in storage
                        if (len > 0) {
                            len = 0;
                            //delete all reports in storage
                            //by setting the length to 0
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                //1 slot offset for buckets length
                                sstore(add(1, bucket.slot), 0)
                            }
                            delete _bucketGlobalState[bucketId];
                        }
                    }
                }
            }
        }
        uint256 reportArrayStartSlot;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            //add 1 for reports offset
            mstore(0x0, add(bucket.slot, 1))
            // hash the reports start slot to get the start of the data
            reportArrayStartSlot := keccak256(0x0, 0x20)
        }

        (uint256 foundIndex, uint256 gcaReportStartSlot) = findReportIndexOrUintMax(reportArrayStartSlot, len);
        handleGlobalBucketStateStore(
            totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, bucketId, foundIndex, gcaReportStartSlot
        );
        handleBucketStore(bucket, foundIndex, totalNewGCC, totalGlwRewardsWeight, totalGRCRewardsWeight, root);
    }

    /**
     * @dev handles the store for a new report in a bucket
     * @param gcaTotalNewGCC - the total amount of new gcc that the gca is reporting
     * @param gcaTotalGlwRewardsWeight - the total amount of glw rewards weight that the gca is reporting
     * @param gcaTotalGRCRewardsWeight - the total amount of grc rewards weight that the gca is reporting
     * @param bucketId - the id of the bucket
     * @param foundIndex - the index of the report in the bucket
     * @param gcaReportStartSlot - the start slot of the gca report
     */
    function handleGlobalBucketStateStore(
        uint256 gcaTotalNewGCC,
        uint256 gcaTotalGlwRewardsWeight,
        uint256 gcaTotalGRCRewardsWeight,
        uint256 bucketId,
        uint256 foundIndex,
        uint256 gcaReportStartSlot
    ) internal {
        uint256 packedGlobalState;
        uint256 slot;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, bucketId)
            mstore(0x20, _bucketGlobalState.slot)
            slot := keccak256(0x0, 0x40)
            packedGlobalState := sload(slot)
        }

        uint256 gccInBucketPlusGcaGcc = (packedGlobalState & _UINT128_MASK) + gcaTotalNewGCC;
        uint256 glwWeightInBucketPlusGcaGlwWeight = (packedGlobalState >> 128 & _UINT64_MASK) + gcaTotalGlwRewardsWeight;
        //No need to shift on `grcWeightInBucketPlusGcaGrcWeight` since  the grcWeight is the last 64 bits
        uint256 grcWeightInBucketPlusGcaGrcWeight = (packedGlobalState >> 192) + gcaTotalGRCRewardsWeight;

        if (foundIndex == 0) {
            //gcc is uint128, glwWeight is uint64, grcWeight is uint64
            packedGlobalState = gccInBucketPlusGcaGcc | (glwWeightInBucketPlusGcaGlwWeight << 128)
                | (grcWeightInBucketPlusGcaGrcWeight << 192);
            // solhint-disable-next-line no-inline-assembly
            assembly {
                sstore(slot, packedGlobalState)
            }
            return;
        }

        uint256 packedDataInReport;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            packedDataInReport := sload(gcaReportStartSlot)
        }

        gccInBucketPlusGcaGcc -= packedDataInReport & _UINT128_MASK;
        glwWeightInBucketPlusGcaGlwWeight -= (packedDataInReport >> 128) & _UINT64_MASK;
        //no need to mask since the grcWeight is the last 64 bits
        grcWeightInBucketPlusGcaGrcWeight -= (packedDataInReport >> 192);

        packedGlobalState = gccInBucketPlusGcaGcc | (glwWeightInBucketPlusGcaGlwWeight << 128)
            | (grcWeightInBucketPlusGcaGrcWeight << 192);
        // solhint-disable-next-line no-inline-assembly
        assembly {
            sstore(slot, packedGlobalState)
        }
    }

    function _transferGlow(address to, uint256 amount) internal override(GCASalaryHelper) {
        GLOW_TOKEN.transfer(to, amount);
    }

    /// @dev claims the glow from inflation
    function _claimGlowFromInflation() internal virtual override(GCASalaryHelper) {
        GLOW_TOKEN.claimGLWFromGCAAndMinerPool();
    }

    /**
     * @dev handles the store for a new report in a bucket
     * @param bucket - the bucket to store the report in
     * @param foundIndex - the index of the report in the bucket
     * @param totalNewGCC - the total amount of new gcc that the gca is reporting
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight that the gca is reporting
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight that the gca is reporting
     * @param root - the merkle root containing all the reports (leaves) for the period
     */
    function handleBucketStore(
        IGCA.Bucket storage bucket,
        uint256 foundIndex,
        uint256 totalNewGCC,
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        bytes32 root
    ) internal {
        //If the array was empty
        // we need to push
        if (foundIndex == 0) {
            bucket.reports.push(
                IGCA.Report({
                    proposingAgent: msg.sender,
                    totalNewGCC: SafeCast.toUint128(totalNewGCC),
                    totalGLWRewardsWeight: SafeCast.toUint64(totalGlwRewardsWeight),
                    totalGRCRewardsWeight: SafeCast.toUint64(totalGRCRewardsWeight),
                    merkleRoot: root
                })
            );
            //else we write the the index we found
        } else {
            bucket.reports[foundIndex == _INDEX_NOT_FOUND ? 0 : foundIndex] = IGCA.Report({
                //Redundant sstore on {proposingAgent}
                proposingAgent: msg.sender,
                totalNewGCC: SafeCast.toUint128(totalNewGCC),
                totalGLWRewardsWeight: SafeCast.toUint64(totalGlwRewardsWeight),
                totalGRCRewardsWeight: SafeCast.toUint64(totalGRCRewardsWeight),
                merkleRoot: root
            });
        }
    }

    /**
     * @dev sets the gca agents
     *         -  removes all previous gca agents
     *         -  sets the new gca agents
     */
    function _setGCAs(address[] memory gcaAddresses) internal {
        gcaAgents = gcaAddresses;
        emit IGCA.NewGCAsAppointed(gcaAddresses);
    }

    /**
     * @dev slashes the gca agents
     * @param gcasToSlash - the gca agents to slash
     */
    function _slashGCAs(address[] memory gcasToSlash) internal {
        unchecked {
            for (uint256 i; i < gcasToSlash.length; ++i) {
                GCASalaryHelper._slash(gcasToSlash[i]);
            }
        }
        emit IGCA.GCAsSlashed(gcasToSlash);
    }

    /* -------------------------------------------------------------------------- */
    /*                        internal / private view functions                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev checks if the weights are valid
     *     - this check is necessary to ensure that GCA's cant cause the weights to overflow in their reports
     *     - and also ensures that the total new gcc minted isnt greated than 200 billion * number of gcas
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight
     * @param totalNewGCC - the total amount of new gcc
     */
    function checkBucketSubmissionArithmeticInputs(
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        uint256 totalNewGCC
    ) internal pure {
        //Arithmetic Checks
        //To make sure that the weight's dont result in an overflow,
        // we need to make sure that the total weight is less than 1/5 of the max uint64
        if (totalGlwRewardsWeight > _UINT64_MAX_DIV5) _revert(IGCA.ReportWeightMustBeLTUint64MaxDiv5.selector);
        if (totalGRCRewardsWeight > _UINT64_MAX_DIV5) _revert(IGCA.ReportWeightMustBeLTUint64MaxDiv5.selector);
        //Max of 1 trillion GCC per week
        //Since there are a max of 5 GCA's at any point in time,
        // this means that the max amount of GCC that can be minted per GCA is 200 Billion
        if (totalNewGCC > _200_BILLION) _revert(IGCA.ReportGCCMustBeLT200Billion.selector);
    }

    /**
     * @dev finds the index of the report in the bucket
     *             - if the report is not found, it returns _INDEX_NOT_FOUND
     * @param reportArrayStartSlot - the storage start slot of the reports
     * @param len - the length of the reports array
     * @return foundIndex - the index of the report in the bucket
     * @return gcaReportStartSlot - the start slot of the report in storage
     */
    function findReportIndexOrUintMax(uint256 reportArrayStartSlot, uint256 len)
        internal
        view
        returns (uint256 foundIndex, uint256)
    {
        unchecked {
            {
                for (uint256 i; i < len; ++i) {
                    address proposingAgent;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        //the address is stored in the [0,1,2] - 3rd slot
                        //                                  ^
                        //that means the slot to read from is i*3 + startSlot + 2
                        proposingAgent := sload(add(reportArrayStartSlot, 2))
                        reportArrayStartSlot := add(reportArrayStartSlot, 3)
                    }
                    if (proposingAgent == msg.sender) {
                        foundIndex = i == 0 ? _INDEX_NOT_FOUND : i;
                        // solhint-disable-next-line no-inline-assembly
                        assembly {
                            //since we incremented the slot by 3, we need to decrement it by 3 to get the start of the packed data
                            reportArrayStartSlot := sub(reportArrayStartSlot, 3)
                        }
                        break;
                    }
                }
            }
        }
        //Increased readability
        uint256 gcaReportStartSlot = reportArrayStartSlot;
        return (foundIndex, gcaReportStartSlot);
    }

    /**
     * @notice returns the length (in seconds) of a bucket duration
     * @return the length (in seconds) of a bucket duration
     */
    function bucketDuration() internal pure virtual override returns (uint256) {
        return _BUCKET_DURATION;
    }

    /**
     * @dev an efficient function to get the merkle root of a bucket at a given index
     * @param bucketId - the bucket id to find the root for
     * @param index - the index of the report in the reports[] array for the bucket
     * @return root - the merkle root for the report for the given bucket at the specific index
     */

    function getBucketRootAtIndexEfficient(uint256 bucketId, uint256 index) internal view returns (bytes32 root) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            //Store the key
            mstore(0x0, bucketId)
            //Store the slot
            mstore(0x20, _buckets.slot)
            //Find storage slot where bucket starts
            let slot := keccak256(0x0, 0x40)
            //Reports start at the second slot so we add 1
            slot := add(slot, 1)

            //Check length
            let len := sload(slot)
            if gt(add(index, 1), len) {
                //cast sig "BucketIndexOutOfBounds()"
                mstore(0x0, 0xfdbe8876)
                revert(0x1c, 0x04)
            }

            mstore(0x0, slot)
            //calculate slot for the reports
            slot := keccak256(0x0, 0x20)
            //slot is now the start of the reports
            //each report is 3 slots long
            //So, our index needs to be multiplied by 3
            index := mul(index, 3)
            //the root is the second slot so we need to add 1
            index := add(index, 1)
            //Calculate the slot to sload from
            slot := add(slot, index)
            //sload the root
            root := sload(slot)
        }

        if (uint256(root) == 0) _revert(IGCA.EmptyRoot.selector);
    }

    /**
     * @dev a function that reverts if proposal hashes are not up to date
     */
    function _revertIfFrozen() internal view {
        if (_isFrozen()) _revert(IGCA.ProposalHashesNotUpdated.selector);
    }

    /// @dev returns true if the contract is frozen, false otherwise
    function _isFrozen() internal view returns (bool) {
        uint256 len = proposalHashes.length;
        //If no proposals have been submitted, we don't need to check
        if (len == 0) return false;
        if (len != nextProposalIndexToUpdate) {
            return true;
        }
        return false;
    }

    /**
     * @dev checks if a bucket is finalized
     * @param bucketLastUpdatedNonce the last updated nonce of the bucket
     * @param bucketFinalizationTimestamp the finalization timestamp of the bucket
     * @param _slashNonce the current slash nonce
     * @return true if the bucket is finalized, false otherwise
     */
    function _isBucketFinalized(
        uint256 bucketLastUpdatedNonce,
        uint256 bucketFinalizationTimestamp,
        uint256 _slashNonce
    ) internal view returns (bool) {
        //If the bft(bucket finalization timestamp) = 0,
        // that means that bucket hasn't been initialized yet
        // so that also means it's not finalized.
        // this also means that we return false if
        // the bucket was indeed finalized. but it was never pushed to
        // in that case, we return a false negative,
        // but it has no side effects since the bucket is empty
        // and no one can claim rewards from it.
        if (bucketFinalizationTimestamp == 0) return false;

        //This checks if the bucket has finalized in regards to the timestamp stored
        bool finalized = block.timestamp >= bucketFinalizationTimestamp;
        //If there hasn't been a slash event and the bucket is finalized
        // then we return true;
        if (bucketLastUpdatedNonce == _slashNonce) {
            if (finalized) return true;
        }

        //If there has been a slash event
        if (bucketLastUpdatedNonce != _slashNonce) {
            //If the slash event happened after the bucket's finalization timestamp
            //That means the bucket had already been finalized and we can return true;
            if (slashNonceToSlashTimestamp[bucketLastUpdatedNonce] >= bucketFinalizationTimestamp) {
                if (finalized) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @dev will underflow and revert if slashNonceToSlashTimestamp[_slashNonce] has not yet been written to
     * @dev returns the WCEIL for the given slash nonce.
     * @dev WCEIL is equal to the end bucket submission time for the bucket that the slash nonce was slashed in + 2 weeks
     * @dev it's two weeks instead of one to make sure there is adequate time for GCA's to submit reports
     * @dev the finalization timestamp is the end of the submission period + 1 week
     */
    function _WCEIL(uint256 _slashNonce) internal view returns (uint256) {
        //This will underflow if slashNonceToSlashTimestamp[_slashNonce] has not yet been written to
        uint256 bucketNonceWasSlashedAt =
            (slashNonceToSlashTimestamp[_slashNonce] - GENESIS_TIMESTAMP) / bucketDuration();
        //the end submission period is the bucket + 2
        return (bucketNonceWasSlashedAt + 2) * bucketDuration() + GENESIS_TIMESTAMP;
    }

    function getPackedBucketGlobalState(uint256 bucketId) internal view returns (uint256 packedGlobalState) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, bucketId)
            mstore(0x20, _bucketGlobalState.slot)
            let slot := keccak256(0x0, 0x40)
            packedGlobalState := sload(slot)
        }
    }

    /**
     * @notice calculates the bucket submission end timestamp
     * @param bucketId - the id of the bucket
     * @param bucketOriginNonce - the original nonce of the bucket
     * @param bucketLastUpdatedNonce - the last updated nonce of the bucket
     * @param _slashNonce - the current slash nonce
     * @param bucketFinalizationTimestamp - the finalization timestamp of the bucket
     * @dev this function is used to calculate the bucket submission start timestamp
     *     - under normal conditions, a bucket should be finalized 2 weeks after its submission period has open
     *     - however, if a slash event occurs, the bucket submission start timestamp will be shifted to the WCEIL() of the slash nonce
     *     - if the slash event occurs after the bucket has been finalized, the bucket submission start timestamp will be shifted to the WCEIL() of the slash nonce
     *         - this is to ensure the gcas have enough time to reinstante proper reports
     */
    function _calculateBucketSubmissionEndTimestamp(
        uint256 bucketId,
        uint256 bucketOriginNonce,
        uint256 bucketLastUpdatedNonce,
        uint256 _slashNonce,
        uint256 bucketFinalizationTimestamp
    ) internal view returns (uint256) {
        // if the bucket has never been initialized
        if (bucketFinalizationTimestamp == 0) return bucketEndSubmissionTimestampNotReinstated(bucketId);
        if (bucketOriginNonce == _slashNonce) return bucketEndSubmissionTimestampNotReinstated(bucketId);
        if (bucketLastUpdatedNonce == _slashNonce) return bucketFinalizationTimestamp;
        uint256 bucketSubmissionStartTimestamp = bucketStartSubmissionTimestampNotReinstated(bucketId);
        //If the slash occurred between the start of the submission period and the bucket finalization timestamp
        for (uint256 i = bucketLastUpdatedNonce; i < _slashNonce;) {
            if (_between(slashNonceToSlashTimestamp[i], bucketSubmissionStartTimestamp, bucketFinalizationTimestamp)) {
                bucketSubmissionStartTimestamp = _WCEIL(i);
            } else {
                break;
            }
            unchecked {
                ++i;
            }
        }
        return bucketSubmissionStartTimestamp;
    }

    /**
     * @dev checks if `a` is between `b` and `c`
     * @param a the number to check
     * @param b the lower bound
     * @param c the upper bound
     * @return true if `a` is between `b` and `c`, false otherwise
     */
    function _between(uint256 a, uint256 b, uint256 c) internal pure returns (bool) {
        return a >= b && a <= c;
    }

    function _genesisTimestamp() internal view virtual override(GCASalaryHelper) returns (uint256) {
        return GENESIS_TIMESTAMP;
    }

    /**
     * @dev calculates the shift to apply to the bitpacked compensation plans
     *     @param index - the index of the gca agent
     *     @return the shift to apply to the bitpacked compensation plans
     */
    function _calculateShift(uint256 index) private pure returns (uint256) {
        return index * _UINT24_SHIFT;
    }

    /* -------------------------------------------------------------------------- */
    /*                             functions to override                           */
    /* -------------------------------------------------------------------------- */
    /// @dev this must be overriden to return the current week in the parent contract
    function _currentWeek() internal view virtual override(GCASalaryHelper) returns (uint256) {
        // solhint-disable-next-line reason-string, custom-errors
        revert();
    }

    /// @dev returns the domain seperator for the current contract, must be overriden
    function _domainSeperatorV4Main() internal view virtual override(GCASalaryHelper) returns (bytes32) {
        // solhint-disable-next-line reason-string, custom-errors
        revert();
    }
}

// File: src/interfaces/IGCA.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IGCA {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                  */
    /* -------------------------------------------------------------------------- */
    error NotGCA();
    error CallerNotGCA();
    error CompensationPlanLengthMustBeGreaterThanZero();
    error InsufficientShares();
    error NoBalanceToPayout();
    error CallerNotGovernance();
    error ProposalHashesNotUpdated();
    error ProposalHashDoesNotMatch();
    error IndexDoesNotMatchNextProposalIndex();
    error ProposalHashesEmpty();
    error ProposalAlreadyUpdated();
    error BucketAlreadyFinalized();
    error ReportGCCMustBeLT200Billion();
    error ReportWeightMustBeLTUint64MaxDiv5();
    error BucketSubmissionNotOpen();
    error BucketSubmissionEnded();
    error EmptyRoot();
    error CallerNotGCAAtIndex();
    error GCCAlreadySet();
    error BucketIndexOutOfBounds();

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev a struct to represent a compensation plan
     * @dev packed into a single uint256
     * @param shares - the amount of shares to be distributed
     * @param agent - the address of the gca agent to receive the shares
     */
    struct ICompensation {
        uint80 shares;
        address agent;
    }

    /**
     * @dev a struct to represent a gca payout
     * @param lastClaimedTimestamp - the last time the gca claimed their payout
     * @param totalSlashableBalance - the total slashable balance of the gca
     */
    struct GCAPayout {
        uint64 lastClaimedTimestamp;
        uint64 maxClaimTimestamp;
        uint128 totalSlashableBalance;
    }

    /**
     * @dev a struct to represent a report
     * @param totalNewGCC - the total amount of new gcc
     * @param totalGLWRewardsWeight - the total amount of glw rewards weight
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight
     * @param merkleRoot - the root containing all the reports (leaves) for the period
     *             - The leaf structure is as follows:
     *                 -   (address payoutWallet,uint256 glwRewardsWeight,uint256 grcRewardsWeight)
     * @param proposingAgent - the address of the gca agent proposing the report
     */
    struct Report {
        uint128 totalNewGCC;
        uint64 totalGLWRewardsWeight;
        uint64 totalGRCRewardsWeight;
        bytes32 merkleRoot;
        address proposingAgent;
    }
    //3 slots

    /**
     * @param originalNonce - the slash nonce in storage at the time of report submission
     * @param lastUpdatedNonce - the slash nonce in storage at the time of the last report submission
     * @param finalizationTimestamp - the finalization timestamp for the bucket according to the weekly bucket schedule
     * @param reports - the reports for the bucket
     */
    struct Bucket {
        uint64 originalNonce;
        uint64 lastUpdatedNonce;
        uint128 finalizationTimestamp;
        Report[] reports;
    }

    /**
     * @dev a struct to represent a bucket global state
     * @dev its used as a caching mechanism to avoid iterating over all buckets
     * @param totalNewGCC - the total amount of new gcc
     * @param totalGLWRewardsWeight - the total amount of glw rewards weight
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight
     */
    struct BucketGlobalState {
        uint128 totalNewGCC;
        uint64 totalGLWRewardsWeight;
        uint64 totalGRCRewardsWeight;
    }

    /* -------------------------------------------------------------------------- */
    /*                                   events                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev Emitted when a gca submits a new compensation plan.
     * @param agent - the address of the gca agent proposing
     * @param plan - the compensation plan of the agent
     */
    event CompensationPlanSubmitted(address indexed agent, uint32[5] plan);

    /**
     * @dev Emitted when a gca claims their payout
     * @param agent - the address of the gca agent claiming
     * @param amount - the amount of tokens claimed
     * @param totalSlashableBalance - the total slashable balance of the gca
     */
    event GCAPayoutClaimed(address indexed agent, uint256 amount, uint256 totalSlashableBalance);

    /**
     * @dev Emitted when a proposal hash is acted upon
     * @param index - the index of the proposal hash inside the {proposalHashes} array
     * @param proposalHash - the proposal hash
     */
    event ProposalHashUpdate(uint256 indexed index, bytes32 proposalHash);

    /**
     * @dev emitted when a proposal hash is pushed
     * @param proposalHash - the proposal hash
     */
    event ProposalHashPushed(bytes32 proposalHash);

    /**
     * @dev Emitted when governacne updates the {requirementsHash}
     * @param requirementsHash - the new requirements hash gcas must abide by
     */
    event RequirementsHashUpdated(bytes32 requirementsHash);

    /**
     * @dev emitted when new GCAs are appointed
     * @dev the new GCAs completely replace the old ones
     * @param newGcas - the new GCAs
     */
    event NewGCAsAppointed(address[] newGcas);

    /**
     * @dev emitted when GCAs are slashed
     * @param slashedGcas - the slashed GCAs
     */
    event GCAsSlashed(address[] slashedGcas);

    /**
     * @notice emitted when a GCA submits a report for a bucket
     * @param bucketId - the id of the bucket
     * @param gca - the address of the gca agent submitting the report
     * @param slashNonce - the slash nonce at the time of report submission
     * @param totalNewGCC - the total amount of new gcc from the farms the GCA is reporting on
     * @param totalGlwRewardsWeight - the total amount of glw rewards weight from the farms the GCA is reporting on
     * @param totalGRCRewardsWeight - the total amount of grc rewards weight from the farms the GCA is reporting on
     * @param root - the merkle root of the reports
     * @param extraData - extra data to be emitted.
     *                         - This extra data can be anything as long as the GCA communicates it to the community
     *                         - and should ideally, if possible, be the leaves of the merkle tree
     */
    event BucketSubmissionEvent(
        uint256 indexed bucketId,
        address gca,
        uint256 slashNonce,
        uint256 totalNewGCC,
        uint256 totalGlwRewardsWeight,
        uint256 totalGRCRewardsWeight,
        bytes32 root,
        bytes extraData
    );

    /* -------------------------------------------------------------------------- */
    /*                                 state changing funcs                       */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice allows governance to push a hash to execute against
     * @param hash - the hash to execute against
     * @param incrementSlashNonce - whether or not to increment the slash nonce
     *         - incrementing the slash nonce means that all non-finalized buckets will be slashed
     *             - and must be reinstated
     * @dev the hash is the abi.encode of the following:
     *         - the gca agents to slash
     *         - the new gca agents
     *         - the proposal creation timestamp
     */
    function pushHash(bytes32 hash, bool incrementSlashNonce) external;

    /**
     * @notice allows governance to change the requirements hash of GCA's
     *         - the requirements hash represents a hash of the duties and responsibilities of a GCA
     * @param  _requirementsHash - the new requirements hash
     */
    function setRequirementsHash(bytes32 _requirementsHash) external;

    /// @dev allows GCAs to submit a compensation plan
    function submitCompensationPlan(uint32[5] calldata plan, uint256 indexOfGCA) external;

    /// @dev allows the contract to pull glow from inflation
    function claimGlowFromInflation() external;

    /* -------------------------------------------------------------------------- */
    /*                                   view functions                            */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns true if the caller is a gca
     * @param account - the address of the account to check
     * @return status -  true if the account is a gca , false otherwise
     */
    function isGCA(address account) external view returns (bool);

    /**
     * @notice returns true if the caller is a gca
     * @param account - the address of the account to check
     * @param index - the index of the gca in the gca array
     * @return status -  true if the account is a gca , false otherwise
     */
    function isGCA(address account, uint256 index) external view returns (bool);

    /// @return - returns all the gcas
    function allGcas() external view returns (address[] memory);

    /**
     * @param gca - the address of the gca to check
     * @return - returns the {GCAPayout} struct data for a gca
     */
    function gcaPayoutData(address gca) external view returns (GCAPayout memory);

    /**
     * @notice - returns all proposal hashes
     * @return proposalHashes - the proposal hashes
     */
    function getProposalHashes() external view returns (bytes32[] memory);

    /**
     * @notice - returns a range of proposal hashes
     * @param start - the start index
     * @param end - the end index
     * @return proposalHashes - the proposal hashes
     */
    function getProposalHashes(uint256 start, uint256 end) external view returns (bytes32[] memory);

    /**
     * @notice returns the global state of a bucket
     * @param bucketId - the id of the bucket
     * @return the global state of a bucket
     */
    function bucketGlobalState(uint256 bucketId) external view returns (BucketGlobalState memory);

    /**
     * @notice returns the {Bucket} struct for a given week / bucketId
     * @param bucketId - the id of the bucket
     * @return bucket - the {Bucket} struct for a given bucketId
     */
    function bucket(uint256 bucketId) external view returns (Bucket memory);

    /**
     * @notice returns if the bucket is finalized or not
     * @param bucketId - the id of the bucket
     */

    function isBucketFinalized(uint256 bucketId) external view returns (bool);
}

// File: src/interfaces/IMinerPool.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

interface IMinerPool {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                    */
    /* -------------------------------------------------------------------------- */
    error ElectricityFuturesSignatureExpired();
    error ElectricityFuturesAuctionEnded();
    error ElectricityFuturesAuctionBidTooLow();
    error ElectricityFuturesAuctionAuthorizationTooLong();
    error ElectricityFuturesAuctionInvalidSignature();
    error ElectricityFutureAuctionBidMustBeGreaterThanMinimumBid();
    error CallerNotEarlyLiquidity();
    error NotUSDCToken();
    error InvalidProof();
    error UserAlreadyClaimed();
    error AlreadyMintedToCarbonCreditAuction();
    error BucketNotFinalized();
    error CallerNotVetoCouncilMember();
    error CannotDelayEmptyBucket();
    error CannotDelayBucketThatNeedsToUpdateSlashNonce();
    error BucketAlreadyDelayed();
    error SignerNotGCA();
    error SignatureDoesNotMatchUser();
    error GlowWeightOverflow();
    error USDCWeightOverflow();
    error GlowWeightGreaterThanTotalWeight();
    error USDCWeightGreaterThanTotalWeight();

    /* -------------------------------------------------------------------------- */
    /*                                     state-changing                        */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Allows anyone to donate USDC into the miner USDC rewards pool
     * @notice the amount is split across 192 weeks starting at the current week + 16
     * @param amount -  amount to deposit
     */
    function donateToUSDCMinerRewardsPool(uint256 amount) external;

    /**
     * @notice Allows the early liquidity to donate USDC into the miner USDC rewards pool
     * @notice the amount is split across 192 weeks starting at the current week + 16
     * @dev the USDC token must be a valid USDC token
     * @dev early liquidity will safeTransfer from the user to the miner pool
     *     -   and then call this function directly.
     *     -   we do this to prevent extra transfers.
     * @param amount -  amount to deposit
     */
    function donateToUSDCMinerRewardsPoolEarlyLiquidity(uint256 amount) external;

    /**
     * @notice allows a user to claim their rewards for a bucket
     * @dev It's highly recommended to use a CLI or UI to call this function.
     *             - the proof can only be generated off-chain with access to the entire tree
     *             - furthermore, USDC tokens must be correctly input in order to receive rewards
     *             - the USDC tokens should be kept on record off-chain.
     *             - failure to input all correct USDC Tokens will result in lost rewards
     * @param bucketId - the id of the bucket
     * @param glwWeight - the weight of the user's glw rewards
     * @param USDCWeight - the weight of the user's USDC rewards
     * @param proof - the merkle proof of the user's rewards
     *                     - the leaves are {payoutWallet, glwWeight, USDCWeight}
     * @param index - the index of the report in the bucket
     *                     - that contains the merkle root where the user's rewards are stored
     * @param user - the address of the user
     * @param claimFromInflation - whether or not to claim glow from inflation
     * @param signature - the eip712 signature that allows a relayer to execute the action
     *               - to claim for a user.
     *               - the relayer is not able to access rewards under any means
     *               - rewards are always sent to the {user}
     */
    function claimRewardFromBucket(
        uint256 bucketId,
        uint256 glwWeight,
        uint256 USDCWeight,
        bytes32[] calldata proof,
        uint256 index,
        address user,
        bool claimFromInflation,
        bytes memory signature
    ) external;

    /**
     * @notice allows a veto council member to delay the finalization of a bucket
     * @dev the bucket must already be initialized in order to be delayed
     * @dev the bucket cannot be finalized in order to be delayed
     * @dev the bucket can be delayed multiple times
     * @param bucketId - the id of the bucket to delay
     */
    function delayBucketFinalization(uint256 bucketId) external;

    /* -------------------------------------------------------------------------- */
    /*                                   view                                    */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns true if a bucket has been delayed
     * @param bucketId - the id of the bucket
     * @return true if the bucket has been delayed
     */
    function hasBucketBeenDelayed(uint256 bucketId) external view returns (bool);

    /**
     * @notice returns the bytes32 digest of the claim reward from bucket message
     * @param bucketId - the id of the bucket
     * @param glwWeight - the weight of the user's glw rewards in the leaf of the report root
     * @param USDCWeight - the weight of the user's USDC rewards in the leaf of the report root
     * @param index - the index of the report in the bucket
     *                     - that contains the merkle root where the user's rewards are stored
     * @param claimFromInflation - whether or not to claim glow from inflation
     * @return the bytes32 digest of the claim reward from bucket message
     */
    function createClaimRewardFromBucketDigest(
        uint256 bucketId,
        uint256 glwWeight,
        uint256 USDCWeight,
        uint256 index,
        bool claimFromInflation
    ) external view returns (bytes32);
}

// File: lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol



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

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        if (nonceAfter != nonceBefore + 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// File: src/MinerPoolAndGCA/BucketSubmission.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {_BUCKET_DURATION} from "@/Constants/Constants.sol";

contract BucketSubmission {
    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the start offset to the current bucket for the grc deposit
     * @dev when depositing grc, the grc is evenly distributed across 192 weeks
     *         -   The first bucket to receive grc is the current bucket + 16 weeks
     *         -   The last bucket to receive grc is the current bucket + 208 weeks
     */
    uint256 public constant OFFSET_LEFT = 16;

    /**
     * @notice the end offset to the current bucket for the grc deposit
     * @dev the amount to offset b(x) by to get the final bucket number where the grc will have finished vesting
     *         - where b(x) is the current bucket
     */
    uint256 public constant OFFSET_RIGHT = 208;

    /// @notice a constant holding the total vesting periods for a grc donation (192)
    uint256 public constant TOTAL_VESTING_PERIODS = OFFSET_RIGHT - OFFSET_LEFT;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev a helper to cache the last updated bucket
     *         -   and the first bucket that USDC was deposited to
     *         -   and the last bucket that USDC was deposited to
     */
    BucketTracker internal bucketTracker;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    /// @notice mappings bucketId -> WeeklyReward
    mapping(uint256 => WeeklyReward) internal rewards;

    /* -------------------------------------------------------------------------- */
    /*                                   structs                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev a helper to keep track of last updated bucket ids for buckets
     * @param lastUpdatedBucket - the last bucket + 16 that grc was deposited to this bucket
     * @param maxBucketId - the lastUpdatedBucket + 191 since the range of buckets is (lastUpdatedBucket, lastUpdatedBucket + 192]
     *                                                                                       ^ inclusive,             exclusive ^
     * @param firstAddedBucketId - the first bucket + 16 that grc was deposited to this bucket
     * @dev none of the params should overflow, since they represent weeks
     *         - it's safe to assume by 2^48 weeks climate should should have better solutions
     */
    struct BucketTracker {
        uint48 lastUpdatedBucket;
        uint48 maxBucketId;
        uint48 firstAddedBucketId;
    }

    /**
     * @dev a struct to help track the amount in weekly rewards
     * @param inheritedFromLastWeek - a flag to see if the bucket has inherited
     *             -   its vesting amount from past buckets
     * @param amountInBucket - the current amount in the bucket available as rewards
     * @param amountToDeduct - the amount to deduct from the {amountInBucket} when it initializes itself
     */
    struct WeeklyReward {
        bool inheritedFromLastWeek;
        uint256 amountInBucket;
        uint256 amountToDeduct;
    }

    /* -------------------------------------------------------------------------- */
    /*                                    events                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Emitted when a user donates usdc to the contract
     * @param bucketId - the bucket id in which the donation happened.
     *        - the result of this donation vests from bucketId + 16 to bucketId + 208
     * @param totalAmountDonated - the total amount donated at `bucketId`
     *         - the total amount donated at `bucketId` is evenly distributed over 192 buckets
     */
    event AmountDonatedToBucket(uint256 indexed bucketId, uint256 totalAmountDonated);

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice returns the current bucket
     * @return currentBucket - the current bucket
     */
    function currentBucket() public view returns (uint256) {
        return (block.timestamp - _genesisTimestamp()) / bucketDuration();
    }

    /**
     * @notice returns the bucket tracker for a given grc token
     * @return bucketTracker - the bucket tracker struct
     */
    function getBucketTracker() external view returns (BucketTracker memory) {
        return bucketTracker;
    }

    /**
     * @notice returns the weekly reward for a given bucket and grc token
     * @param id - the bucketId (week) to query for
     * @return bucket - the  weekly reward struct for the bucket
     */
    function reward(uint256 id) public view returns (WeeklyReward memory) {
        (WeeklyReward memory bucket,) = _rewardWithNeedsInitializing(id);
        return bucket;
    }

    /* -------------------------------------------------------------------------- */
    /*                             internal add to bucket                         */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice adds the usdc to the current bucket
     * @dev this function is called when a user donates usdc to the contract
     * @param amount - the amount of usdc to add
     *                  - the `amount` gets distributed over 192 buckets with the first bucket being the current bucket + OFFSET_LEFT
     */
    function _addToCurrentBucket(uint256 amount) internal {
        //Calculate the current bucket
        uint256 currentBucketId = currentBucket();
        //The bucket to add to is always the current bucket + OFFSET_LEFT
        uint256 bucketToAddTo = currentBucketId + OFFSET_LEFT;
        //The bucket to deduct from is always the bucketToAddTo + TOTAL_VESTING_PERIODS
        uint256 bucketToDeductFrom = bucketToAddTo + TOTAL_VESTING_PERIODS;

        //The amount to add to the bucketToAddTo OR subtract from the bucketToDeductFrom
        uint256 amountToAddOrSubtract = amount / TOTAL_VESTING_PERIODS;

        //Load  bucketTracker into memory
        //Bucket trackers are used to keep track of the last updated bucket
        //and are used for caching to reduce gas costs
        BucketTracker memory _bucketTracker = bucketTracker;

        //Load the current bucket into memory
        WeeklyReward memory currentWeeklyReward = rewards[bucketToAddTo];

        //If the bucket has already reconciled with its past weeks,
        //then we can just add the amount to the bucket
        //We also deduct the amount from the bucketToDeductFrom bucket
        if (currentWeeklyReward.inheritedFromLastWeek) {
            rewards[bucketToAddTo].amountInBucket += amountToAddOrSubtract;
            rewards[bucketToDeductFrom].amountToDeduct += amountToAddOrSubtract;
            emit AmountDonatedToBucket(currentBucketId, amount);
            return;
        }

        //Cache the last updated bucket
        //The last updated bucket is the last bucket thats {amountInBucket} was updated
        //If the last updated bucket has never been set (aka == 0),
        //then that means the first bucket to be updated is the bucketToAddTo
        //If the last updated bucket was already set, then we use that
        uint256 lastUpdatedBucket =
            _bucketTracker.lastUpdatedBucket == 0 ? bucketToAddTo : _bucketTracker.lastUpdatedBucket;
        WeeklyReward memory lastBucket = rewards[lastUpdatedBucket];

        //We already know we are going to add {amountToAddOrSubtract} to the {bucketToDeductFrom}
        rewards[bucketToDeductFrom].amountToDeduct += amountToAddOrSubtract;

        //This means that we don't need to look backwards
        //Since all the vested amount from that bucket would have been emptied by now if the bucket hadnt been refreshed in 192 weeks
        // If the lastUpdatedBucket is the current bucket, we also don't need to look backwards
        //If the {bucketToAddTo} is greater than the {maxBucketId} then we don't need to look backwards
        //This is so because if {bucketToAddTo} is > {maxBucketId} then that means that all the tokens have already vested
        //because tokens vest in between {bucketToAddTo} and {maxBucketId}
        //This would only be the case if there has been a long period of time where no one has called {claimRewards}
        //Or, no one has donated the grc to the contract
        //Also, if the last bucket is the same as the bucket to add to, then we don't need to look backwards neither
        bool pastDataIrrelevant = bucketToAddTo > _bucketTracker.maxBucketId || lastUpdatedBucket == bucketToAddTo;
        //If past data is irrelevant, we can assume that we start fresh from the current bucket
        uint256 totalToDeductFromBucket = pastDataIrrelevant ? 0 : currentWeeklyReward.amountToDeduct;

        //As such, we don't need to look backwards if the past data is irrelevant
        if (!pastDataIrrelevant) {
            //However, if the past data is relavant,
            //We start at the last bucket that was updated,
            //And we look forwards until we reach the bucketToAddTo
            for (uint256 i = lastUpdatedBucket; i < bucketToAddTo; ++i) {
                totalToDeductFromBucket += rewards[i].amountToDeduct;
            }
        } else {
            //If the past data is irrelevant, then we set the amount in the bucket to 0
            //Such that the write below does not incorrectly add to the bucket
            lastBucket.amountInBucket = 0;
        }

        /**
         * We then set
         *         {
         *             amountInBucket: (lastBucket.amountInBucket + amountToAddOrSubtract) - totalToDeductFromBucket,
         *             amountToDeduct: 0,
         *             inheritedFromLastWeek: true
         *         }
         *         We know that lastBucket.amountInBucket will always have a value > 0 (if the bucket has been donated to),
         *         and we also know that every time a bucket is donated to, it becomes the last updated bucket,
         *         therefore, {lastBucket.amountInBucket} is intended to be a cumulative sum of all the donations
         *         with {totalToDeductFromBucket} being the amount that is needed to be deducted from the bucket
         *         Once we adjust the amount in the bucket, we set the {inheritedFromLastWeek} to true
         *         We also set the {amountToDeduct} to 0 since we don't need to deduct anything from the bucket anymore
         */
        rewards[bucketToAddTo] = WeeklyReward({
            inheritedFromLastWeek: true,
            amountInBucket: (lastBucket.amountInBucket + amountToAddOrSubtract) - totalToDeductFromBucket,
            amountToDeduct: 0
        });

        //If the lastUpdatedBucket has changed, then we update the lastUpdatedBucket
        if (_bucketTracker.lastUpdatedBucket != bucketToAddTo) {
            bucketTracker = BucketTracker(
                uint48(bucketToAddTo),
                uint48(bucketToAddTo + TOTAL_VESTING_PERIODS - 1),
                _bucketTracker.firstAddedBucketId
            );
        }

        emit AmountDonatedToBucket(currentBucketId, amount);
    }

    /* -------------------------------------------------------------------------- */
    /*                                internal helpers                            */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev gets the total amount of grc in a bucket that is available to withdraw and initializes it
     *             - this is a helper function only meant to be used inside the claimRewards function
     * @param id - the id of the bucket
     */
    function _getAmountForTokenAndInitIfNot(uint256 id) internal returns (uint256) {
        (WeeklyReward memory weeklyReward, bool needsInitializing) = _rewardWithNeedsInitializing(id);
        if (needsInitializing) {
            weeklyReward.inheritedFromLastWeek = true;
            weeklyReward.amountToDeduct = 0;
            rewards[id] = weeklyReward;
        }
        return weeklyReward.amountInBucket;
    }

    /* -------------------------------------------------------------------------- */
    /*                                 internal view                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns the weekly reward for a given bucket
     * @dev if the bucket has not yet been initialized,
     *             - the function will look backwards to calculate the correct amount
     *             - if the bucket has been initialized, it will return the bucket
     * @param id - the bucketId (week) to query for
     * @return bucket - the  weekly reward struct for the bucket
     * @return needsInitializing -- flag to see if the bucket needs to be initialized
     * @dev `needsInitializing` should be used in the withdraw reward function to see if the bucket needs to be initialized
     */
    function _rewardWithNeedsInitializing(uint256 id) private view returns (WeeklyReward memory, bool) {
        WeeklyReward memory bucket = rewards[id];
        // If the bucket has already been initialized
        // Then we can just return the bucket.
        if (bucket.inheritedFromLastWeek || id < OFFSET_LEFT) {
            return (bucket, false);
        }

        // If the index to search for is greater than the maxBucketId
        // than that means all the tokens would have vested,
        // So we return the empty bucket
        BucketTracker memory _bucketTracker = bucketTracker;
        if (id > _bucketTracker.maxBucketId) {
            return (bucket, false);
        }

        uint256 amountToSubtract = bucket.amountToDeduct;
        //Can't underflow since we start at id 16
        uint256 lastBucketId = id - 1;

        //We get the first added bucket id from the bucket tracker.
        //The tracker helps us prevent uneccessary backward lookups
        uint256 firstUpdatedBucket = _bucketTracker.firstAddedBucketId;
        while (true) {
            // if the firstUpdatedbucket is greater than the last bucket id
            //then we break out of the loop
            //This happens in the case where the bucket has not been initialized yet
            //And also in the case where we re-add a grc token to the contract
            // after all its vesting periods have ended
            if (firstUpdatedBucket > lastBucketId) {
                break;
            }
            //Load the last bucket into memory
            WeeklyReward memory lastBucket = rewards[lastBucketId--];
            // add the amount to deduct from the last bucket to the amount to subtract
            amountToSubtract += lastBucket.amountToDeduct;

            //If the last bucket has inherited from the last week
            if (lastBucket.inheritedFromLastWeek) {
                //We set the amount in the bucket to the last bucket amount - the amount to subtract
                //This marks the point at which we can stop looking backwards
                //It's also important to keep in mind that this algorithm only works
                //because we know that the last bucket will always have a value
                //If it does not have a value -- that means that the bucket has not been initialized
                // and therefore there are no rewards that need to be accounted for in those buckets
                bucket.amountInBucket = lastBucket.amountInBucket - amountToSubtract;
                break;
            }
        }
        return (bucket, true);
    }

    function bucketDuration() internal pure virtual returns (uint256) {
        return _BUCKET_DURATION;
    }
    /* -------------------------------------------------------------------------- */
    /*                              functions to override                         */
    /* -------------------------------------------------------------------------- */
    /// @dev this must be overriden inside the parent contract.

    function _genesisTimestamp() internal view virtual returns (uint256) {
        return 0;
    }
}

// File: lib/solady/src/utils/MerkleProofLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*            MERKLE PROOF VERIFICATION OPERATIONS            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(proof) {
                // Initialize `offset` to the offset of `proof` elements in memory.
                let offset := add(proof, 0x20)
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(offset, shl(5, mload(proof)))
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, mload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), mload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(proof.offset, shl(5, proof.length))
                // Initialize `offset` to the offset of `proof` in the calldata.
                let offset := proof.offset
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, calldataload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), calldataload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    function verifyMultiProof(
        bytes32[] memory proof,
        bytes32 root,
        bytes32[] memory leaves,
        bool[] memory flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // Cache the lengths of the arrays.
            let leavesLength := mload(leaves)
            let proofLength := mload(proof)
            let flagsLength := mload(flags)

            // Advance the pointers of the arrays to point to the data.
            leaves := add(0x20, leaves)
            proof := add(0x20, proof)
            flags := add(0x20, flags)

            // If the number of flags is correct.
            for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flagsLength) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := mul(iszero(iszero(flagsLength)), add(proof, shl(5, proofLength)))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                leavesLength := shl(5, leavesLength)
                for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
                    mstore(add(hashesFront, i), mload(add(leaves, i)))
                }
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, leavesLength)
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flagsLength := add(hashesBack, shl(5, flagsLength))

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(mload(flags)) {
                        // Loads the next proof.
                        b := mload(proof)
                        proof := add(proof, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag.
                    flags := add(flags, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flagsLength)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required (i.e. `proofEnd != 0`).
                        or(iszero(proofEnd), eq(proofEnd, proof))
                    )
                break
            }
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    function verifyMultiProofCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32[] calldata leaves,
        bool[] calldata flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // If the number of flags is correct.
            for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flags.length) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    // forgefmt: disable-next-item
                    isValid := eq(
                        calldataload(
                            xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
                        ),
                        root
                    )
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd :=
                    mul(iszero(iszero(flags.length)), add(proof.offset, shl(5, proof.length)))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, shl(5, leaves.length))
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flags.length := add(hashesBack, shl(5, flags.length))

                // We don't need to make a copy of `proof.offset` or `flags.offset`,
                // as they are pass-by-value (this trick may not always save gas).

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(calldataload(flags.offset)) {
                        // Loads the next proof.
                        b := calldataload(proof.offset)
                        proof.offset := add(proof.offset, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag offset.
                    flags.offset := add(flags.offset, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flags.length)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required (i.e. `proofEnd != 0`).
                        or(iszero(proofEnd), eq(proofEnd, proof.offset))
                    )
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes32 array.
    function emptyProof() internal pure returns (bytes32[] calldata proof) {
        /// @solidity memory-safe-assembly
        assembly {
            proof.length := 0
        }
    }

    /// @dev Returns an empty calldata bytes32 array.
    function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
        /// @solidity memory-safe-assembly
        assembly {
            leaves.length := 0
        }
    }

    /// @dev Returns an empty calldata bool array.
    function emptyFlags() internal pure returns (bool[] calldata flags) {
        /// @solidity memory-safe-assembly
        assembly {
            flags.length := 0
        }
    }
}

// File: src/SafetyDelay.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IVetoCouncil} from "@/interfaces/IVetoCouncil.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @dev Struct representing a holding of tokens in the HoldingContract.
 * @param amount The amount of tokens being held.
 * @param expirationTimestamp The timestamp at which the holding expires and can be withdrawn.
 */
struct Holding {
    uint192 amount;
    uint64 expirationTimestamp;
}

/**
 * @dev a helper type to organize claim holdings arguments
 * @param user the address of the user
 * @param token the address of the USDC token to withdraw
 */
struct ClaimHoldingArgs {
    address user;
    address token;
}

interface ISafetyDelay {
    function addHolding(address user, address token, uint192 amount) external;
    function holdings(address user, address token) external view returns (Holding memory);
    function claimHoldings(ClaimHoldingArgs[] memory args) external;
}

/**
 * @title SafetyDelay
 * @notice This contract is used to hold tokens for users
 *         - This contract holds all USDC tokens that are part of the protocol
 *         - Once farms withdraw, there is a 1 week delay before they can claim their tokens
 *         - The Miner Pool Contract assigns these holdings as part of the withdraw process
 *         - Veto Agents can delay all withdrawals by 13 weeks
 *         - A holding can be max delayed for 97 days
 */
contract SafetyDelay is ISafetyDelay {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error OnlyMinerPoolCanAddHoldings();
    error WithdrawalNotReady();
    error CallerMustBeVetoCouncilMember();
    error DelayStillOnCooldown();
    error NetworkIsFrozen();
    error AlreadyWithdrawnFromHolding();
    error MinerPoolAlreadySet();

    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the default delay for withdrawals
     * @dev the default delay is 7 days
     * Whenever a user withdraws from the miner pool,
     *       their funds are locked for 7 days
     */
    uint256 public constant DEFAULT_DELAY = uint256(7 days);

    /**
     * @dev 90 days in seconds
     */
    uint256 public constant NINETY_DAYS = uint256(90 days);

    /**
     * @notice the delay for withdrawals after the network is delayed
     * @dev the delay is 13 weeks
     * all withdrawals will be delayed for 13 weeks
     */
    uint256 public constant VETO_HOLDING_DELAY = uint256(13 weeks);

    /**
     * @dev a cached version of five weeks in seconds
     * @dev used in delayNetwork to ensure that the network can only be delayed every 5 weeks
     * @dev This helps prevent bad veto agents from spamming the delay network function
     *         - by giving governance enough time to kick out the veto agent
     */
    uint256 public constant FIVE_WEEKS = uint256(5 weeks);

    /* -------------------------------------------------------------e------------- */
    /*                                 immutables                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the address of the veto council
     * @dev veto council members can delay the network
     */
    IVetoCouncil public immutable VETO_COUNCIL;

    /**
     * @notice the address of the miner pool
     * @dev this is the address that can add holdings to the contract
     */
    address public immutable MINER_POOL;

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice the minimum timestamp for withdrawals
     * @dev any claims below this timestamp will revert
     */
    uint256 public minimumWithdrawTimestamp;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice the holdings for each user
     *     Note: We could have chosen an array of holdings
     *     such that each withdraw truly is a FIFO queue with 1 week delay
     *     However, we chose to store all holdings in a single slot
     *     to avoid cold sstores and sloads
     *     The downside of this approach is that we can't have a FIFO queue
     *     and that any time a withdraw is made from the miner pool contract
     *     the user's holdings are locked for 7 days
     */
    mapping(address => mapping(address => Holding)) private _holdings;

    /* -------------------------------------------------------------------------- */
    /*                                   events                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev emitted when there is a network delay
     * @param vetoAgent the address of the veto agent that delayed the network
     * @param timestamp the timestamp at which the network was delayed
     */
    event NetworkDelay(address vetoAgent, uint256 timestamp);

    /**
     * @dev emitted whenever a holding is added to a user
     * @param user the address of the user
     * @param token the address of the USDC token
     * @param amount the amount of tokens added to the holding
     * @dev we dont emit a {HoldingClaimed} event since there may be a tax
     *     - on the token that will mess up the data.
     *     - we rely on catching transfer events
     */
    event HoldingAdded(address indexed user, address indexed token, uint192 amount);

    /*
        * @notice emitted when a user claims their holding
        * @param user the address of the user
        * @param token the address of the USDC token
        * @param amount the amount of tokens claimed
    */
    event HoldingClaimed(address indexed user, address indexed token, uint192 amount);

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */

    /**
     * @param _vetoCouncil the address of the veto council
     * @param _minerPool the address of the miner pool
     */
    constructor(address _vetoCouncil, address _minerPool) payable {
        VETO_COUNCIL = IVetoCouncil(_vetoCouncil);
        MINER_POOL = _minerPool;
    }

    /* -------------------------------------------------------------------------- */
    /*                                    delay                                   */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice allows veto council members to delay the network by 13 weeks
     */
    function delayNetwork() external {
        if (!VETO_COUNCIL.isCouncilMember(msg.sender)) {
            _revert(CallerMustBeVetoCouncilMember.selector);
        }
        uint256 _minimumWithdrawTimestamp = minimumWithdrawTimestamp;
        if (_minimumWithdrawTimestamp == 0) {
            minimumWithdrawTimestamp = block.timestamp + VETO_HOLDING_DELAY;
            emit NetworkDelay(msg.sender, block.timestamp);
            return;
        }
        if (block.timestamp < _minimumWithdrawTimestamp) {
            //The block.timestamp needs to be within 5 weeks of
            //minimumWithdrawTimestamp
            uint256 timeLeftInDelay = _minimumWithdrawTimestamp - block.timestamp;
            if (timeLeftInDelay > FIVE_WEEKS) {
                _revert(DelayStillOnCooldown.selector);
            }
        }

        minimumWithdrawTimestamp = block.timestamp + VETO_HOLDING_DELAY;
        emit NetworkDelay(msg.sender, block.timestamp);
    }

    /* -------------------------------------------------------------------------- */
    /*                                   claim                                    */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice entrypoint to claim holdings
     * @param args - an array of {ClaimHoldingArgs}
     * @dev this is a batch method to claim holdings
     *     - this is more gas efficient than calling claimHolding for each holding
     *     - the protocol may use a relayer to bundle claims
     */
    function claimHoldings(ClaimHoldingArgs[] memory args) external {
        //If the network is frozen, don't allow withdrawals
        bool networkIsFrozen = isNetworkFrozen();
        //Loop over all the arguments
        uint256 len = args.length;
        for (uint256 i; i < len;) {
            ClaimHoldingArgs memory arg = args[i];
            _claimHolding(arg.user, arg.token, networkIsFrozen);
            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice entrypoint to claim a single holding
     * @param user the address of the user
     * @param token the address of the USDC token to withdraw
     * @dev should be used if the user only wants to claim their holding
     */
    function claimHoldingSingleton(address user, address token) external {
        // If the network is frozen and timestamp since expiration is not more than 90 days, don't allow withdrawals
        bool networkIsFrozen = isNetworkFrozen();
        _claimHolding(user, token, networkIsFrozen);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 add holdings                               */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice an internal method to increment the amount in a holding
     * @param user the address of the user
     * @param token the address of the USDC token to withdraw
     * @param amount the amount of tokens to add to the holding
     */
    function addHolding(address user, address token, uint192 amount) external {
        if (msg.sender != MINER_POOL) {
            _revert(OnlyMinerPoolCanAddHoldings.selector);
        }
        _holdings[user][token].amount += amount;
        _holdings[user][token].expirationTimestamp = uint64(block.timestamp + DEFAULT_DELAY);
        emit HoldingAdded(user, token, amount);
    }

    /* -------------------------------------------------------------------------- */
    /*                                 view functions                             */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns the Holding struct for a user and token pair
     * @param user the address of the user
     * @param token the address of the USDC token to withdraw
     * @return holding - the Holding struct
     */
    function holdings(address user, address token) external view returns (Holding memory) {
        return _holdings[user][token];
    }

    /**
     * @notice returns true if the network is frozen
     * @dev the network is frozen if the minimumWithdrawTimestamp is greater than the current block timestamp
     * @return isNetworkFrozen - true if the network is frozen
     */
    function isNetworkFrozen() public view returns (bool) {
        return block.timestamp < minimumWithdrawTimestamp;
    }

    /**
     * @dev checks if the holding is available to be withdrawn
     * @param holdingExpirationTimestamp the timestamp at which the holding expires
     * @param isNetworkFrozen whether or not the network is currently frozen
     * @dev - if the network is frozen, the holding can be withdrawn only if it's been more than 90 days past the expiration of the holding
     *      - if the network is not frozen, the holding can be withdrawn only if it's past the expiration date of the holding
     */
    function checkHoldingAvailable(uint64 holdingExpirationTimestamp, bool isNetworkFrozen) internal view {
        if (block.timestamp < holdingExpirationTimestamp) {
            _revert(WithdrawalNotReady.selector);
        }
        //Can't underflow because of the check above
        //No claim should be able to be held for more than 97 days
        //If it's been less than than 97 days since the proposal has expired,
        //(expiration timestamp is always claim timestamp + 1 week, so )
        //in order for proposal to be held maximum 97 days,
        //We need to check if the diff is 90 days
        if (block.timestamp - holdingExpirationTimestamp < NINETY_DAYS) {
            if (isNetworkFrozen) {
                _revert(NetworkIsFrozen.selector);
            }
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                                   utils                                    */
    /* -------------------------------------------------------------------------- */

    /**
     * @dev an internal method to claim a holding
     * @param user the address of the user
     * @param token the address of the USDC token to withdraw
     * @param networkIsFrozen whether or not the network is currently frozen
     */
    function _claimHolding(address user, address token, bool networkIsFrozen) internal {
        Holding memory holding = _holdings[user][token];
        checkHoldingAvailable(holding.expirationTimestamp, networkIsFrozen);
        //Delete the holding args.
        //Should set all the data to zero.
        delete _holdings[user][token];
        //Add the amount to the amount to transfer
        SafeERC20.safeTransfer(IERC20(token), user, holding.amount);
        emit HoldingClaimed(user, token, holding.amount);
    }

    /**
     * @dev more efficient reverts
     * @param selector the selector of the error
     */

    function _revert(bytes4 selector) internal pure {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0, selector)
            revert(0, 4)
        }
    }
}

// File: src/Constants/Constants.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

uint256 constant _BUCKET_DURATION = uint256(7 days);
uint256 constant _GENESIS_TIMESTAMP = 1700352000;

// File: lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {Strings} from "../Strings.sol";

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `hash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32 digest) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(
        address validator,
        bytes memory data
    ) internal pure returns (bytes32 digest) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol



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

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as an immutable variable.
 *
 * Strings of arbitrary length can be optimized using this library if
 * they are short enough (up to 31 bytes) by packing them with their
 * length (1 byte) in a single EVM word (32 bytes). Additionally, a
 * fallback mechanism can be used for every other case.
 *
 * Usage example:
 *
 * ```solidity
 * contract Named {
 *     using ShortStrings for *;
 *
 *     ShortString private immutable _name;
 *     string private _nameFallback;
 *
 *     constructor(string memory contractName) {
 *         _name = contractName.toShortStringWithFallback(_nameFallback);
 *     }
 *
 *     function name() external view returns (string memory) {
 *         return _name.toStringWithFallback(_nameFallback);
 *     }
 * }
 * ```
 */
library ShortStrings {
    // Used as an identifier for strings longer than 31 bytes.
    bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;

    error StringTooLong(string str);
    error InvalidShortString();

    /**
     * @dev Encode a string of at most 31 chars into a `ShortString`.
     *
     * This will trigger a `StringTooLong` error is the input string is too long.
     */
    function toShortString(string memory str) internal pure returns (ShortString) {
        bytes memory bstr = bytes(str);
        if (bstr.length > 31) {
            revert StringTooLong(str);
        }
        return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
    }

    /**
     * @dev Decode a `ShortString` back to a "normal" string.
     */
    function toString(ShortString sstr) internal pure returns (string memory) {
        uint256 len = byteLength(sstr);
        // using `new string(len)` would work locally but is not memory safe.
        string memory str = new string(32);
        /// @solidity memory-safe-assembly
        assembly {
            mstore(str, len)
            mstore(add(str, 0x20), sstr)
        }
        return str;
    }

    /**
     * @dev Return the length of a `ShortString`.
     */
    function byteLength(ShortString sstr) internal pure returns (uint256) {
        uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
        if (result > 31) {
            revert InvalidShortString();
        }
        return result;
    }

    /**
     * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
     */
    function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
        if (bytes(value).length < 32) {
            return toShortString(value);
        } else {
            StorageSlot.getStringSlot(store).value = value;
            return ShortString.wrap(_FALLBACK_SENTINEL);
        }
    }

    /**
     * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     */
    function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return toString(value);
        } else {
            return store;
        }
    }

    /**
     * @dev Return the length of a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
     *
     * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
     * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
     */
    function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
        if (ShortString.unwrap(value) != _FALLBACK_SENTINEL) {
            return byteLength(value);
        } else {
            return bytes(store).length;
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol



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

pragma solidity ^0.8.20;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

// File: lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC1271 standard signature validation method for
 * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
 */
interface IERC1271 {
    /**
     * @dev Should return whether the signature provided is valid for the provided data
     * @param hash      Hash of the data to be signed
     * @param signature Signature byte array associated with _data
     */
    function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
}

// File: src/libraries/HalfLifeCarbonCreditAuction.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {ABDKMath64x64} from "@/libraries/ABDKMath64x64.sol";

library HalfLifeCarbonCreditAuction {
    /**
     * @dev the halving period in seconds (7 days)
     * @dev the price of the carbon credit auction decays with a half-life or 7 days
     *         - the price will shrink exponentially every 7 days unless there are purchases
     */
    uint256 constant HALVING_PERIOD = uint256(7 days);

    /**
     * @notice calculates the value remaining after a given amount of time has elapsed
     *         - using a half-life of 52 weeks
     * @param initialValue the initial value
     * @param elapsedSeconds the number of seconds that have elapsed
     * @return value - the value remaining given a half-life of 52 weeks
     */
    function calculateHalfLifeValue(uint256 initialValue, uint256 elapsedSeconds) public pure returns (uint256) {
        if (elapsedSeconds == 0) {
            return initialValue;
        }
        // Convert the half-life from months to seconds
        uint256 halfLifeSeconds = HALVING_PERIOD;

        // Calculate the ratio of elapsed time to half-life in fixed point format
        int128 tOverT =
            ABDKMath64x64.div(ABDKMath64x64.fromUInt(elapsedSeconds), ABDKMath64x64.fromUInt(halfLifeSeconds));

        // Calculate (1/2)^(t/T) using the fact that e^(ln(0.5)*t/T) = (0.5)^(t/T)
        int128 halfPowerTOverT =
            ABDKMath64x64.exp(ABDKMath64x64.mul(ABDKMath64x64.ln(ABDKMath64x64.divu(1, 2)), tOverT));

        // Calculate the final amount
        uint256 finalValue = ABDKMath64x64.mulu(halfPowerTOverT, initialValue);

        return finalValue;
    }
}

// File: src/MinerPoolAndGCA/GCASalaryHelper.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {VestingMathLib} from "@/libraries/VestingMathLib.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {_BUCKET_DURATION} from "@/Constants/Constants.sol";

abstract contract GCASalaryHelper {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error HashesNotUpdated();
    error CannotSetNonceToZero();
    error InvalidRelaySignature();
    error InvalidGCAHash();
    error InvalidUserIndex();
    error InvalidShares();
    error SlashedAgentCannotClaimReward();

    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */

    /// @dev 10_000 GLW Per Week available as rewards to all GCAs
    uint256 public constant REWARDS_PER_SECOND_FOR_ALL = 10_000 ether / uint256(7 days);

    /**
     * @notice the amount of shares required per agent when submitting a compensation plan
     * @dev this is not strictly enforced, but rather the
     *         the total shares in a comp plan must equal the SHARES_REQUIRED_PER_COMP_PLAN
     */
    uint256 public constant SHARES_REQUIRED_PER_COMP_PLAN = 100_000;

    /// @dev the type hash for a claim payout relay permit
    bytes32 public constant CLAIM_PAYOUT_RELAY_PERMIT_TYPEHASH =
        keccak256("ClaimPayoutRelay(address relayer,uint256 paymentNonce,uint256 relayNonce)");

    /* -------------------------------------------------------------------------- */
    /*                                 state vars                                */
    /* -------------------------------------------------------------------------- */

    ///  Private payment nonce.
    /// Private payment nonce only needs to be incremented when a gca submits a new overriding comp plan.
    /// The public paymentNonce() function is also incremented whenever there's a slash event
    /// The public paymentNonce() function should be the _privatePaymentNonce + proposalHashes.length;
    uint256 private _privatePaymentNonce;

    /* -------------------------------------------------------------------------- */
    /*                                   mappings                                  */
    /* -------------------------------------------------------------------------- */
    //payment nonce -> gca index -> comp plan
    mapping(uint256 => mapping(uint256 => uint32[5])) private _paymentNonceToCompensationPlan;
    //payment nonce -> shift start timestamp
    mapping(uint256 => uint256) private _paymentNonceToShiftStartTimestamp;

    // agent -> payment nonce -> amount already withdrawn
    mapping(address => mapping(uint256 => uint256)) public amountWithdrawnAtPaymentNonce;

    /// @dev slashed agents cannot claim rewards
    mapping(address => bool) public isSlashed;

    // paymentNonce -> keccak256(abi.encodePacked(address[]));
    mapping(uint256 => bytes32) private _paymentNonceToGCAs;

    /// @notice the next nonce to use in the relay signature
    mapping(address => uint256) public nextRelayNonce;

    /* -------------------------------------------------------------------------- */
    /*                                 constructor                                */
    /* -------------------------------------------------------------------------- */
    /**
     * @param startingAgents the starting gca agents
     */
    constructor(address[] memory startingAgents) payable {
        if (startingAgents.length == 0) return;
        _paymentNonceToGCAs[0] = keccak256(abi.encodePacked(startingAgents));
        unchecked {
            for (uint256 i; i < startingAgents.length; ++i) {
                //starting payment nonce is 0
                //so we set the comp plan for all the agents to the identity matrix
                //for the first payment nonce
                _paymentNonceToCompensationPlan[0][i] = defaultCompPlan(i);
            }
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                               claiming payout                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev we don't need a deadline on the sig since the relayer cant make the funds go anywhere else,
     *             except for the user's address.
     *             AND - the relayer is restricted to a certian nonce.
     * @param user the user to claim the payout for
     * @param paymentNonce the payment nonce to claim the payout for
     * @param activeGCAsAtPaymentNonce the active gca agents at the payment nonce
     * @param userIndex the index of the user in the active gca agents array
     * @param claimFromInflation whether or not to claim glow from inflation
     * @param sig the relay signature
     */

    function claimPayout(
        address user,
        uint256 paymentNonce,
        address[] calldata activeGCAsAtPaymentNonce,
        uint256 userIndex,
        bool claimFromInflation,
        bytes memory sig
    ) external {
        if (isSlashed[user]) {
            _revert(SlashedAgentCannotClaimReward.selector);
        }
        if (msg.sender != user) {
            bytes32 digest = createRelayDigest(msg.sender, paymentNonce, nextRelayNonce[user]++);
            if (!SignatureChecker.isValidSignatureNow(user, digest, sig)) {
                _revert(InvalidRelaySignature.selector);
            }
        }
        if (claimFromInflation) {
            _claimGlowFromInflation();
        }
        (uint256 withdrawableAmount,, uint256 amountAlreadyWithdrawn) =
            getPayoutData(user, paymentNonce, activeGCAsAtPaymentNonce, userIndex);
        amountWithdrawnAtPaymentNonce[user][paymentNonce] = amountAlreadyWithdrawn + withdrawableAmount;
        _transferGlow(user, withdrawableAmount);
    }

    /* -------------------------------------------------------------------------- */
    /*                                view functions                              */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns the bytes32 digest used for the relay signature
     * @param relayer the relayer that is being granted permission
     * @param paymentNonce the payment nonce that the relayer is being granted permission for
     * @param relayNonce the relay nonce that the relayer is being granted permission for
     * @return digest - the bytes32 digest
     */
    function createRelayDigest(address relayer, uint256 paymentNonce, uint256 relayNonce)
        public
        view
        returns (bytes32)
    {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                _domainSeperatorV4Main(),
                keccak256(abi.encode(CLAIM_PAYOUT_RELAY_PERMIT_TYPEHASH, relayer, paymentNonce, relayNonce))
            )
        );
        return digest;
    }

    /**
     * @notice gets the payout data for an agent
     * @param user the user to get the payout data for
     * @param paymentNonce the payment nonce to get the payout data for
     * @param activeGCAsAtPaymentNonce the active gca agents at the payment nonce
     * @param userIndex the index of the user in the active gca agents array
     * @dev the function must take in the activeGCAsAtPaymentNonce array to prevent
     *         -   a user from submitting a different array of gca agents
     *         -   and receiving false payout data
     */
    function getPayoutData(
        address user,
        uint256 paymentNonce,
        address[] calldata activeGCAsAtPaymentNonce,
        uint256 userIndex
    ) public view returns (uint256 withdrawableAmount, uint256 slashableAmount, uint256 amountAlreadyWithdrawn) {
        if (keccak256(abi.encodePacked(activeGCAsAtPaymentNonce)) != _paymentNonceToGCAs[paymentNonce]) {
            _revert(InvalidGCAHash.selector);
        }
        if (user != activeGCAsAtPaymentNonce[userIndex]) {
            _revert(InvalidUserIndex.selector);
        }
        uint256 userShares;
        uint256 len = activeGCAsAtPaymentNonce.length;
        unchecked {
            for (uint256 i; i < len; ++i) {
                userShares += _paymentNonceToCompensationPlan[paymentNonce][i][userIndex];
            }
        }
        amountAlreadyWithdrawn = amountWithdrawnAtPaymentNonce[user][paymentNonce];

        uint256 shiftStartTimestamp = _paymentNonceToShiftStartTimestamp[paymentNonce];
        uint256 shiftEndTimestamp = _paymentNonceToShiftStartTimestamp[paymentNonce + 1];
        if (shiftEndTimestamp == 0) {
            shiftEndTimestamp = block.timestamp;
        } else {
            shiftEndTimestamp = _min(shiftEndTimestamp, block.timestamp);
        }
        uint256 secondsWorked = shiftEndTimestamp - shiftStartTimestamp;
        uint256 secondsStopped;
        if (block.timestamp > shiftEndTimestamp) {
            secondsStopped = block.timestamp - shiftEndTimestamp;
        }
        uint256 totalShares = len * SHARES_REQUIRED_PER_COMP_PLAN;

        uint256 rewardPerSecond = userShares * REWARDS_PER_SECOND_FOR_ALL / totalShares;

        (withdrawableAmount, slashableAmount) = VestingMathLib.calculateWithdrawableAmountAndSlashableAmount(
            rewardPerSecond, secondsWorked, secondsStopped, amountAlreadyWithdrawn
        );

        return (withdrawableAmount, slashableAmount, amountAlreadyWithdrawn);
    }

    /**
     * @notice returns the shift start timestamp for a payment nonce
     * @param nonce the payment nonce to get the shift start timestamp for
     * @return shiftStartTimestamp - the shift start timestamp for the payment nonce or 0 if it does not exist
     */
    function paymentNonceToShiftStartTimestamp(uint256 nonce) external view returns (uint256) {
        return _paymentNonceToShiftStartTimestamp[nonce];
    }

    /**
     * @notice returns the gca agents hash for a payment nonce
     * @param nonce the payment nonce to get the gca agents hash for
     * @return gcaHash - the gca agents hash for the payment nonce
     */
    function payoutNonceToGCAHash(uint256 nonce) external view returns (bytes32) {
        return _paymentNonceToGCAs[nonce];
    }

    /**
     * @notice returns the comp plan for a payment nonce and gca index
     * @param nonce the payment nonce to get the comp plan for
     * @param index the gca index to get the comp plan for
     * @return shares - the comp plan for the payment nonce and gca index
     */
    function paymentNonceToCompensationPlan(uint256 nonce, uint256 index) external view returns (uint32[5] memory) {
        return _paymentNonceToCompensationPlan[nonce][index];
    }

    /**
     * @notice returns the current payment nonce in storage
     * @return paymentNonce - the current payment nonce
     */
    function paymentNonce() public view returns (uint256) {
        return _privatePaymentNonce;
    }

    /// @dev should only be used once in the constructor of GCA
    function setZeroPaymentStartTimestamp() internal {
        _paymentNonceToShiftStartTimestamp[0] = _genesisTimestamp();
    }

    /* -------------------------------------------------------------------------- */
    /*                                  internal                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice slashes an agent
     * @param user the user to slash
     */

    function _slash(address user) internal {
        isSlashed[user] = true;
    }

    /**
     * @param compPlan the comp plans to submit
     * @param indexOfGCA the index of the gca submitting the comp plan
     * @param totalGCAs the total number of gca agents
     */
    function handleCompensationPlanSubmission(uint32[5] calldata compPlan, uint256 indexOfGCA, uint256 totalGCAs)
        internal
    {
        uint256 totalShares;
        for (uint256 i; i < totalGCAs; ++i) {
            totalShares += compPlan[i];
        }
        if (totalShares != SHARES_REQUIRED_PER_COMP_PLAN) {
            _revert(InvalidShares.selector);
        }

        //Get the current payment nonce.
        uint256 _paymentNonce = paymentNonce();
        uint256 nextPaymentNonce = _paymentNonce + 1;

        uint256 currentShiftStartTimestamp = _paymentNonceToShiftStartTimestamp[_paymentNonce];

        /**
         * When we create a new comp plan, we increment the payment nonce by 1.
         *         We only increment the nonce when the comp. period has actually begun.
         *
         *         For example, if we're in comp period 1, and we submit a new comp plan for comp period 2,
         *         we initialize comp period 2 to start at block.timestamp + `bucketDuration()`,
         *         Therefore, there is a 1 week period where the comp plan is not active and comp plan 1
         *         is still being acted upon, BUT, the nonce has already been incremented.
         *
         *         Therefore, that means that {currentShiftStartTimestamp} is the start of period 2,
         *         and if block.timestamp is LESS than that, that means that comp period 2 has not started
         *         and all comp. plans that are submitted will have an effect on comp period 2.
         *
         *         Once block.timestamp is greater than {currentShiftStartTimestamp}, that means that
         *         comp period 2 has started, and all comp plans submitted will have an effect on comp period 3.
         *
         *         This keeps going on and on and on.
         */

        /**
         * This evaluates as the initializer for the comp plan being proposed.
         */
        if (block.timestamp > currentShiftStartTimestamp) {
            //We need to increment the nonce
            _paymentNonceToShiftStartTimestamp[nextPaymentNonce] = block.timestamp + bucketDuration();

            //Make sure that all the hashes are updated
            bytes32 gcaHash = _paymentNonceToGCAs[_paymentNonce];
            _paymentNonceToGCAs[nextPaymentNonce] = gcaHash;
            for (uint256 i; i < totalGCAs; ++i) {
                if (i == indexOfGCA) {
                    _paymentNonceToCompensationPlan[nextPaymentNonce][i] = compPlan;
                } else {
                    _paymentNonceToCompensationPlan[nextPaymentNonce][i] =
                        _paymentNonceToCompensationPlan[_paymentNonce][i];
                }
            }
            _privatePaymentNonce = nextPaymentNonce;
            return;
        }

        //If we are still in the current week, we need to put the comp plan
        //in the current payment nonce (which is the next upcoming plan).

        _paymentNonceToCompensationPlan[_paymentNonce][indexOfGCA] = compPlan;
    }

    /**
     * @param gcaAgents the gca agents
     * @dev handles incrementing payment nonce,
     *             - setting the gca agents hash
     *             - setting the shift start timestamp
     *             - setting the comp plans to the identity matrix
     *                 - (i.e. each gca agent gets 100_000 shares)
     */
    function callbackInElectionEvent(address[] memory gcaAgents) internal {
        uint256 _paymentNonce = paymentNonce();
        uint256 currentShiftStartTimestamp = _paymentNonceToShiftStartTimestamp[_paymentNonce];

        //If the current bucket has started, we move to the next bucket
        if (block.timestamp > currentShiftStartTimestamp) {
            ++_paymentNonce;
            _privatePaymentNonce = _paymentNonce;
        }

        //Set the gca agents hash
        _paymentNonceToGCAs[_paymentNonce] = keccak256(abi.encodePacked(gcaAgents));
        _paymentNonceToShiftStartTimestamp[_paymentNonce] = block.timestamp;
        //All the reports in here need to be set to a identity matrix
        unchecked {
            for (uint256 i; i < gcaAgents.length; ++i) {
                _paymentNonceToCompensationPlan[_paymentNonce][i] = defaultCompPlan(i);
            }
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                         internal view/pure functions                       */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice returns the default comp plan for a gca agent
     * @param gcaIndex the index of the gca agent
     * @dev the default comp plan is the identity matrix
     * @return shares - the default comp plan for a gca agent at index {gcaIndex}
     */
    function defaultCompPlan(uint256 gcaIndex) internal pure returns (uint32[5] memory shares) {
        shares[gcaIndex] = uint32(SHARES_REQUIRED_PER_COMP_PLAN);
        return shares;
    }

    /**
     * @notice returns the bucket duration
     * @return bucketDuration - the bucket duration
     */
    function bucketDuration() internal pure virtual returns (uint256) {
        return _BUCKET_DURATION;
    }

    /* -------------------------------------------------------------------------- */
    /*                            functions to override                           */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice claims glow from inflation
     * @dev the function must be overriden by the parent contract
     */
    function _claimGlowFromInflation() internal virtual;

    /**
     * @notice returns the domain seperator for the relay signature
     * @dev the function must be overriden by the parent contract
     * @return domainSeperator - the domain seperator for the relay signature
     */
    function _domainSeperatorV4Main() internal view virtual returns (bytes32);
    /**
     * @notice returns the genesis timestamp of the glow protocol
     * @return genesisTimestamp - the genesis timestamp of the glow protocol
     * @dev the function must be overriden by the parent contract
     */
    function _genesisTimestamp() internal view virtual returns (uint256);
    /**
     * @notice returns the current week
     * @return week - the current week
     * @dev the function must be overriden by the parent contract
     */
    function _currentWeek() internal view virtual returns (uint256);

    /**
     * @notice transfers glow to an address
     * @param to the address to transfer glow to
     * @param amount the amount of glow to transfer
     * @dev the function must be overriden by the parent contract
     */
    function _transferGlow(address to, uint256 amount) internal virtual;

    /* -------------------------------------------------------------------------- */
    /*                                    utils                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @dev returns the min of (a,b)
     * @param a the first number
     * @param b the second number
     * @return min - the min of (a,b)
     */
    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @notice More efficiently reverts with a bytes4 selector
     * @param selector The selector to revert with
     */
    function _revert(bytes4 selector) internal pure {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/Address.sol



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

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/Strings.sol



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

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

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

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @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), _HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(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) {
        uint256 localValue = value;
        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] = _HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        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 bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

// File: src/libraries/ABDKMath64x64.sol



// SPDX-License-Identifier: BSD-4-Clause
/*
 * ABDK Math 64.64 Smart Contract Library.  Copyright © 2019 by ABDK Consulting.
 * Author: Mikhail Vladimirov <mikhail.vladimirov@gmail.com>
 */
pragma solidity ^0.8.0;

/**
 * Smart contract library of mathematical functions operating with signed
 * 64.64-bit fixed point numbers.  Signed 64.64-bit fixed point number is
 * basically a simple fraction whose numerator is signed 128-bit integer and
 * denominator is 2^64.  As long as denominator is always the same, there is no
 * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
 * represented by int128 type holding only the numerator.
 */
library ABDKMath64x64 {
    /*
    * Minimum value signed 64.64-bit fixed point number may have. 
    */
    int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;

    /*
    * Maximum value signed 64.64-bit fixed point number may have. 
    */
    int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

    /**
     * Convert signed 256-bit integer number into signed 64.64-bit fixed point
     * number.  Revert on overflow.
     *
     * @param x signed 256-bit integer number
     * @return signed 64.64-bit fixed point number
     */
    function fromInt(int256 x) internal pure returns (int128) {
        unchecked {
            require(x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);
            return int128(x << 64);
        }
    }

    /**
     * Convert signed 64.64 fixed point number into signed 64-bit integer number
     * rounding down.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64-bit integer number
     */
    function toInt(int128 x) internal pure returns (int64) {
        unchecked {
            return int64(x >> 64);
        }
    }

    /**
     * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
     * number.  Revert on overflow.
     *
     * @param x unsigned 256-bit integer number
     * @return signed 64.64-bit fixed point number
     */
    function fromUInt(uint256 x) internal pure returns (int128) {
        unchecked {
            require(x <= 0x7FFFFFFFFFFFFFFF);
            return int128(int256(x << 64));
        }
    }

    /**
     * Convert signed 64.64 fixed point number into unsigned 64-bit integer
     * number rounding down.  Revert on underflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @return unsigned 64-bit integer number
     */
    function toUInt(int128 x) internal pure returns (uint64) {
        unchecked {
            require(x >= 0);
            return uint64(uint128(x >> 64));
        }
    }

    /**
     * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point
     * number rounding down.  Revert on overflow.
     *
     * @param x signed 128.128-bin fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function from128x128(int256 x) internal pure returns (int128) {
        unchecked {
            int256 result = x >> 64;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Convert signed 64.64 fixed point number into signed 128.128 fixed point
     * number.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 128.128 fixed point number
     */
    function to128x128(int128 x) internal pure returns (int256) {
        unchecked {
            return int256(x) << 64;
        }
    }

    /**
     * Calculate x + y.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function add(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            int256 result = int256(x) + y;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate x - y.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function sub(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            int256 result = int256(x) - y;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate x * y rounding down.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function mul(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            int256 result = int256(x) * y >> 64;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point
     * number and y is signed 256-bit integer number.  Revert on overflow.
     *
     * @param x signed 64.64 fixed point number
     * @param y signed 256-bit integer number
     * @return signed 256-bit integer number
     */
    function muli(int128 x, int256 y) internal pure returns (int256) {
        unchecked {
            if (x == MIN_64x64) {
                require(
                    y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
                        && y <= 0x1000000000000000000000000000000000000000000000000
                );
                return -y << 63;
            } else {
                bool negativeResult = false;
                if (x < 0) {
                    x = -x;
                    negativeResult = true;
                }
                if (y < 0) {
                    y = -y; // We rely on overflow behavior here
                    negativeResult = !negativeResult;
                }
                uint256 absoluteResult = mulu(x, uint256(y));
                if (negativeResult) {
                    require(absoluteResult <= 0x8000000000000000000000000000000000000000000000000000000000000000);
                    return -int256(absoluteResult); // We rely on overflow behavior here
                } else {
                    require(absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                    return int256(absoluteResult);
                }
            }
        }
    }

    /**
     * Calculate x * y rounding down, where x is signed 64.64 fixed point number
     * and y is unsigned 256-bit integer number.  Revert on overflow.
     *
     * @param x signed 64.64 fixed point number
     * @param y unsigned 256-bit integer number
     * @return unsigned 256-bit integer number
     */
    function mulu(int128 x, uint256 y) internal pure returns (uint256) {
        unchecked {
            if (y == 0) return 0;

            require(x >= 0);

            uint256 lo = (uint256(int256(x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;
            uint256 hi = uint256(int256(x)) * (y >> 128);

            require(hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
            hi <<= 64;

            require(hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);
            return hi + lo;
        }
    }

    /**
     * Calculate x / y rounding towards zero.  Revert on overflow or when y is
     * zero.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function div(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            require(y != 0);
            int256 result = (int256(x) << 64) / y;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate x / y rounding towards zero, where x and y are signed 256-bit
     * integer numbers.  Revert on overflow or when y is zero.
     *
     * @param x signed 256-bit integer number
     * @param y signed 256-bit integer number
     * @return signed 64.64-bit fixed point number
     */
    function divi(int256 x, int256 y) internal pure returns (int128) {
        unchecked {
            require(y != 0);

            bool negativeResult = false;
            if (x < 0) {
                x = -x; // We rely on overflow behavior here
                negativeResult = true;
            }
            if (y < 0) {
                y = -y; // We rely on overflow behavior here
                negativeResult = !negativeResult;
            }
            uint128 absoluteResult = divuu(uint256(x), uint256(y));
            if (negativeResult) {
                require(absoluteResult <= 0x80000000000000000000000000000000);
                return -int128(absoluteResult); // We rely on overflow behavior here
            } else {
                require(absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
                return int128(absoluteResult); // We rely on overflow behavior here
            }
        }
    }

    /**
     * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
     * integer numbers.  Revert on overflow or when y is zero.
     *
     * @param x unsigned 256-bit integer number
     * @param y unsigned 256-bit integer number
     * @return signed 64.64-bit fixed point number
     */
    function divu(uint256 x, uint256 y) internal pure returns (int128) {
        unchecked {
            require(y != 0);
            uint128 result = divuu(x, y);
            require(result <= uint128(MAX_64x64));
            return int128(result);
        }
    }

    /**
     * Calculate -x.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function neg(int128 x) internal pure returns (int128) {
        unchecked {
            require(x != MIN_64x64);
            return -x;
        }
    }

    /**
     * Calculate |x|.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function abs(int128 x) internal pure returns (int128) {
        unchecked {
            require(x != MIN_64x64);
            return x < 0 ? -x : x;
        }
    }

    /**
     * Calculate 1 / x rounding towards zero.  Revert on overflow or when x is
     * zero.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function inv(int128 x) internal pure returns (int128) {
        unchecked {
            require(x != 0);
            int256 result = int256(0x100000000000000000000000000000000) / x;
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function avg(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            return int128((int256(x) + int256(y)) >> 1);
        }
    }

    /**
     * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.
     * Revert on overflow or in case x * y is negative.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function gavg(int128 x, int128 y) internal pure returns (int128) {
        unchecked {
            int256 m = int256(x) * int256(y);
            require(m >= 0);
            require(m < 0x4000000000000000000000000000000000000000000000000000000000000000);
            return int128(sqrtu(uint256(m)));
        }
    }

    /**
     * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number
     * and y is unsigned 256-bit integer number.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @param y uint256 value
     * @return signed 64.64-bit fixed point number
     */
    function pow(int128 x, uint256 y) internal pure returns (int128) {
        unchecked {
            bool negative = x < 0 && y & 1 == 1;

            uint256 absX = uint128(x < 0 ? -x : x);
            uint256 absResult;
            absResult = 0x100000000000000000000000000000000;

            if (absX <= 0x10000000000000000) {
                absX <<= 63;
                while (y != 0) {
                    if (y & 0x1 != 0) {
                        absResult = absResult * absX >> 127;
                    }
                    absX = absX * absX >> 127;

                    if (y & 0x2 != 0) {
                        absResult = absResult * absX >> 127;
                    }
                    absX = absX * absX >> 127;

                    if (y & 0x4 != 0) {
                        absResult = absResult * absX >> 127;
                    }
                    absX = absX * absX >> 127;

                    if (y & 0x8 != 0) {
                        absResult = absResult * absX >> 127;
                    }
                    absX = absX * absX >> 127;

                    y >>= 4;
                }

                absResult >>= 64;
            } else {
                uint256 absXShift = 63;
                if (absX < 0x1000000000000000000000000) {
                    absX <<= 32;
                    absXShift -= 32;
                }
                if (absX < 0x10000000000000000000000000000) {
                    absX <<= 16;
                    absXShift -= 16;
                }
                if (absX < 0x1000000000000000000000000000000) {
                    absX <<= 8;
                    absXShift -= 8;
                }
                if (absX < 0x10000000000000000000000000000000) {
                    absX <<= 4;
                    absXShift -= 4;
                }
                if (absX < 0x40000000000000000000000000000000) {
                    absX <<= 2;
                    absXShift -= 2;
                }
                if (absX < 0x80000000000000000000000000000000) {
                    absX <<= 1;
                    absXShift -= 1;
                }

                uint256 resultShift = 0;
                while (y != 0) {
                    require(absXShift < 64);

                    if (y & 0x1 != 0) {
                        absResult = absResult * absX >> 127;
                        resultShift += absXShift;
                        if (absResult > 0x100000000000000000000000000000000) {
                            absResult >>= 1;
                            resultShift += 1;
                        }
                    }
                    absX = absX * absX >> 127;
                    absXShift <<= 1;
                    if (absX >= 0x100000000000000000000000000000000) {
                        absX >>= 1;
                        absXShift += 1;
                    }

                    y >>= 1;
                }

                require(resultShift < 64);
                absResult >>= 64 - resultShift;
            }
            int256 result = negative ? -int256(absResult) : int256(absResult);
            require(result >= MIN_64x64 && result <= MAX_64x64);
            return int128(result);
        }
    }

    /**
     * Calculate sqrt (x) rounding down.  Revert if x < 0.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function sqrt(int128 x) internal pure returns (int128) {
        unchecked {
            require(x >= 0);
            return int128(sqrtu(uint256(int256(x)) << 64));
        }
    }

    /**
     * Calculate binary logarithm of x.  Revert if x <= 0.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function log_2(int128 x) internal pure returns (int128) {
        unchecked {
            require(x > 0);

            int256 msb = 0;
            int256 xc = x;
            if (xc >= 0x10000000000000000) {
                xc >>= 64;
                msb += 64;
            }
            if (xc >= 0x100000000) {
                xc >>= 32;
                msb += 32;
            }
            if (xc >= 0x10000) {
                xc >>= 16;
                msb += 16;
            }
            if (xc >= 0x100) {
                xc >>= 8;
                msb += 8;
            }
            if (xc >= 0x10) {
                xc >>= 4;
                msb += 4;
            }
            if (xc >= 0x4) {
                xc >>= 2;
                msb += 2;
            }
            if (xc >= 0x2) msb += 1; // No need to shift xc anymore

            int256 result = msb - 64 << 64;
            uint256 ux = uint256(int256(x)) << uint256(127 - msb);
            for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {
                ux *= ux;
                uint256 b = ux >> 255;
                ux >>= 127 + b;
                result += bit * int256(b);
            }

            return int128(result);
        }
    }

    /**
     * Calculate natural logarithm of x.  Revert if x <= 0.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function ln(int128 x) internal pure returns (int128) {
        unchecked {
            require(x > 0);

            return int128(int256(uint256(int256(log_2(x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));
        }
    }

    /**
     * Calculate binary exponent of x.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function exp_2(int128 x) internal pure returns (int128) {
        unchecked {
            require(x < 0x400000000000000000); // Overflow

            if (x < -0x400000000000000000) return 0; // Underflow

            uint256 result = 0x80000000000000000000000000000000;

            if (x & 0x8000000000000000 > 0) {
                result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
            }
            if (x & 0x4000000000000000 > 0) {
                result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
            }
            if (x & 0x2000000000000000 > 0) {
                result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
            }
            if (x & 0x1000000000000000 > 0) {
                result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
            }
            if (x & 0x800000000000000 > 0) {
                result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
            }
            if (x & 0x400000000000000 > 0) {
                result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
            }
            if (x & 0x200000000000000 > 0) {
                result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
            }
            if (x & 0x100000000000000 > 0) {
                result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
            }
            if (x & 0x80000000000000 > 0) {
                result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
            }
            if (x & 0x40000000000000 > 0) {
                result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
            }
            if (x & 0x20000000000000 > 0) {
                result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
            }
            if (x & 0x10000000000000 > 0) {
                result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
            }
            if (x & 0x8000000000000 > 0) {
                result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
            }
            if (x & 0x4000000000000 > 0) {
                result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
            }
            if (x & 0x2000000000000 > 0) {
                result = result * 0x1000162E525EE054754457D5995292026 >> 128;
            }
            if (x & 0x1000000000000 > 0) {
                result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
            }
            if (x & 0x800000000000 > 0) {
                result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
            }
            if (x & 0x400000000000 > 0) {
                result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
            }
            if (x & 0x200000000000 > 0) {
                result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;
            }
            if (x & 0x100000000000 > 0) {
                result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
            }
            if (x & 0x80000000000 > 0) {
                result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
            }
            if (x & 0x40000000000 > 0) {
                result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
            }
            if (x & 0x20000000000 > 0) {
                result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
            }
            if (x & 0x10000000000 > 0) {
                result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
            }
            if (x & 0x8000000000 > 0) {
                result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
            }
            if (x & 0x4000000000 > 0) {
                result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
            }
            if (x & 0x2000000000 > 0) {
                result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
            }
            if (x & 0x1000000000 > 0) {
                result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
            }
            if (x & 0x800000000 > 0) {
                result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
            }
            if (x & 0x400000000 > 0) {
                result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
            }
            if (x & 0x200000000 > 0) {
                result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
            }
            if (x & 0x100000000 > 0) {
                result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
            }
            if (x & 0x80000000 > 0) {
                result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
            }
            if (x & 0x40000000 > 0) {
                result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
            }
            if (x & 0x20000000 > 0) {
                result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;
            }
            if (x & 0x10000000 > 0) {
                result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
            }
            if (x & 0x8000000 > 0) {
                result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
            }
            if (x & 0x4000000 > 0) {
                result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
            }
            if (x & 0x2000000 > 0) {
                result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
            }
            if (x & 0x1000000 > 0) {
                result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;
            }
            if (x & 0x800000 > 0) {
                result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
            }
            if (x & 0x400000 > 0) {
                result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;
            }
            if (x & 0x200000 > 0) {
                result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;
            }
            if (x & 0x100000 > 0) {
                result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;
            }
            if (x & 0x80000 > 0) {
                result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
            }
            if (x & 0x40000 > 0) {
                result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
            }
            if (x & 0x20000 > 0) {
                result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;
            }
            if (x & 0x10000 > 0) {
                result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
            }
            if (x & 0x8000 > 0) {
                result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
            }
            if (x & 0x4000 > 0) {
                result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;
            }
            if (x & 0x2000 > 0) {
                result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;
            }
            if (x & 0x1000 > 0) {
                result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
            }
            if (x & 0x800 > 0) {
                result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
            }
            if (x & 0x400 > 0) {
                result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;
            }
            if (x & 0x200 > 0) {
                result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;
            }
            if (x & 0x100 > 0) {
                result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;
            }
            if (x & 0x80 > 0) {
                result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
            }
            if (x & 0x40 > 0) {
                result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
            }
            if (x & 0x20 > 0) {
                result = result * 0x100000000000000162E42FEFA39EF366F >> 128;
            }
            if (x & 0x10 > 0) {
                result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;
            }
            if (x & 0x8 > 0) {
                result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
            }
            if (x & 0x4 > 0) {
                result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
            }
            if (x & 0x2 > 0) {
                result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;
            }
            if (x & 0x1 > 0) {
                result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;
            }

            result >>= uint256(int256(63 - (x >> 64)));
            require(result <= uint256(int256(MAX_64x64)));

            return int128(int256(result));
        }
    }

    /**
     * Calculate natural exponent of x.  Revert on overflow.
     *
     * @param x signed 64.64-bit fixed point number
     * @return signed 64.64-bit fixed point number
     */
    function exp(int128 x) internal pure returns (int128) {
        unchecked {
            require(x < 0x400000000000000000); // Overflow

            if (x < -0x400000000000000000) return 0; // Underflow

            return exp_2(int128(int256(x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));
        }
    }

    /**
     * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
     * integer numbers.  Revert on overflow or when y is zero.
     *
     * @param x unsigned 256-bit integer number
     * @param y unsigned 256-bit integer number
     * @return unsigned 64.64-bit fixed point number
     */
    function divuu(uint256 x, uint256 y) private pure returns (uint128) {
        unchecked {
            require(y != 0);

            uint256 result;

            if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                result = (x << 64) / y;
            } else {
                uint256 msb = 192;
                uint256 xc = x >> 192;
                if (xc >= 0x100000000) {
                    xc >>= 32;
                    msb += 32;
                }
                if (xc >= 0x10000) {
                    xc >>= 16;
                    msb += 16;
                }
                if (xc >= 0x100) {
                    xc >>= 8;
                    msb += 8;
                }
                if (xc >= 0x10) {
                    xc >>= 4;
                    msb += 4;
                }
                if (xc >= 0x4) {
                    xc >>= 2;
                    msb += 2;
                }
                if (xc >= 0x2) msb += 1; // No need to shift xc anymore

                result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);
                require(result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

                uint256 hi = result * (y >> 128);
                uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);

                uint256 xh = x >> 192;
                uint256 xl = x << 64;

                if (xl < lo) xh -= 1;
                xl -= lo; // We rely on overflow behavior here
                lo = hi << 128;
                if (xl < lo) xh -= 1;
                xl -= lo; // We rely on overflow behavior here

                result += xh == hi >> 128 ? xl / y : 1;
            }

            require(result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
            return uint128(result);
        }
    }

    /**
     * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
     * number.
     *
     * @param x unsigned 256-bit integer number
     * @return unsigned 128-bit integer number
     */
    function sqrtu(uint256 x) private pure returns (uint128) {
        unchecked {
            if (x == 0) {
                return 0;
            } else {
                uint256 xx = x;
                uint256 r = 1;
                if (xx >= 0x100000000000000000000000000000000) {
                    xx >>= 128;
                    r <<= 64;
                }
                if (xx >= 0x10000000000000000) {
                    xx >>= 64;
                    r <<= 32;
                }
                if (xx >= 0x100000000) {
                    xx >>= 32;
                    r <<= 16;
                }
                if (xx >= 0x10000) {
                    xx >>= 16;
                    r <<= 8;
                }
                if (xx >= 0x100) {
                    xx >>= 8;
                    r <<= 4;
                }
                if (xx >= 0x10) {
                    xx >>= 4;
                    r <<= 2;
                }
                if (xx >= 0x4) r <<= 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1;
                r = (r + x / r) >> 1; // Seven iterations should be enough
                uint256 r1 = x / r;
                return uint128(r < r1 ? r : r1);
            }
        }
    }
}

// File: src/libraries/VestingMathLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

uint256 constant VESTING_PERIODS = 100;
/// @dev the maximum amount of seconds a second can vest for
/// @dev this is to prevent a second from over-vesting in payout
/// @dev since rewards vest at 1% per week, this is 100 weeks
uint256 constant MAX_VESTING_SECONDS = uint256(7 days) * 100;

library VestingMathLib {
    /**
     * @dev Find total owed now and slashable balance using the summation of an arithmetic series
     * @dev formula = n/2 * (2a + (n-1)d) or n/2 * (a + l)
     * @dev read more about this  https://github.com/glowlabs-org/glow-docs/issues/4
     * @param rewardsPerSecond - the amount of glow per second the agent earns
     * @param secondsActive - the amount of seconds the agent has worked on a given shift
     * @param secondsStopped - the amount of seconds since the agent has stopped working on their shift
     * @param amountAlreadyWithdrawn - the amount of glow already withdrawn by the agent
     * @return withdrawableAmount - the amount of glow owed now
     * @return slashableAmount - the total slashable amount of glow (total owed - withdrawableAmount)
     */
    function calculateWithdrawableAmountAndSlashableAmount(
        uint256 rewardsPerSecond,
        uint256 secondsActive,
        uint256 secondsStopped,
        uint256 amountAlreadyWithdrawn
    ) internal pure returns (uint256 withdrawableAmount, uint256 slashableAmount) {
        //Placeholder for fully vested seconds.
        uint256 fullyVestedSeconds;

        //If (secondsActive + secondsStopped) > MAX_VESTING_SECONDS,
        //That means that there are some seconds that are fully vested.
        if (secondsActive + secondsStopped > MAX_VESTING_SECONDS) {
            //The fully vested seconds are as follows:
            fullyVestedSeconds = secondsActive + secondsStopped - MAX_VESTING_SECONDS;
        }

        //We make sure that the fully vested seconds are not greater than the seconds active.
        //This can happen as secondsStopped grows once the agent stops working
        if (fullyVestedSeconds > secondsActive) {
            fullyVestedSeconds = secondsActive;
        }

        //The fully vested rewards are a result of the fully vested seconds * the rewards per second.
        uint256 fullyVestedRewards = rewardsPerSecond * fullyVestedSeconds;

        //The partially vested seconds are the seconds active minus the fully vested seconds.
        uint256 partiallyVestedSeconds = secondsActive - fullyVestedSeconds;

        uint256 lowestValueSecond = (1 + secondsStopped) * rewardsPerSecond / MAX_VESTING_SECONDS;

        uint256 highestValueSecond = (secondsActive + secondsStopped) * rewardsPerSecond / MAX_VESTING_SECONDS;
        if (highestValueSecond > rewardsPerSecond) {
            highestValueSecond = rewardsPerSecond;
        }

        //Arithmetic series
        uint256 partiallyVestedSecondsValue = partiallyVestedSeconds * (lowestValueSecond + highestValueSecond) / 2;

        uint256 totalRewards = secondsActive * rewardsPerSecond;
        withdrawableAmount = fullyVestedRewards + partiallyVestedSecondsValue;
        slashableAmount = totalRewards - withdrawableAmount;
        withdrawableAmount -= amountAlreadyWithdrawn;

        return (withdrawableAmount, slashableAmount);
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/Math.sol



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

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // 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 (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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 towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (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 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                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.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 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.

            uint256 twos = denominator & (0 - denominator);
            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 (unsignedRoundsUp(rounding) && 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
     * towards zero.
     *
     * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// File: lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol



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

pragma solidity ^0.8.20;

/**
 * @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);
        }
    }
}