// File: src/MerkleDistributorPeriphery.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {IMerkleDistributorWithDeadline} from "src/uniswap/interfaces/IMerkleDistributorWithDeadline.sol";
import {MerkleDistributorWithDeadline} from "src/uniswap/MerkleDistributorWithDeadline.sol";
import {IMerkleDistributorPeriphery} from "src/interfaces/IMerkleDistributorPeriphery.sol";
import {IOwnable2Step} from "src/interfaces/IOwnable2Step.sol";
import {Amount} from "src/types/Amount.sol";
import {Id} from "src/types/Id.sol";
import {Index} from "src/types/Index.sol";
import {MerkleProof} from "src/types/MerkleProof.sol";
import {MerkleRoot} from "src/types/MerkleRoot.sol";
import {Number} from "src/types/Number.sol";
import {Timestamp} from "src/types/Timestamp.sol";

/// @title A periphery contract implementation to manage multiple Merkle Distributor contracts.
/// @author Timelord
contract MerkleDistributorPeriphery is
    IMerkleDistributorPeriphery,
    Ownable2Step
{
    using SafeERC20 for IERC20;

    /// @dev Mapping storage from id to the distributor contract addresses.
    IMerkleDistributorWithDeadline[] private storedDistributors;

    mapping(Id => address) private ownerships;
    mapping(IMerkleDistributorWithDeadline distributor => address)
        private ownershipGivenDistributors;

    address private queuedDeployer;
    IERC20 private queuedToken;
    Amount private queuedTotalAmount;
    MerkleRoot private queuedMerkleRoot;
    Timestamp private queuedEndTime;

    /// @notice Sets the contract deployer as the owner.
    constructor(address chosenOwner) Ownable(chosenOwner) {}

    /// @inheritdoc IMerkleDistributorPeriphery
    function merkleDistributor(
        Id id
    ) external view override returns (IMerkleDistributorWithDeadline) {
        return storedDistributors[Id.unwrap(id)];
    }

    /// @inheritdoc IMerkleDistributorPeriphery
    function totalMerkleDistributors() external view override returns (Number) {
        return Number.wrap(storedDistributors.length);
    }

    function ownerGivenId(Id id) external view override returns (address) {
        if (Id.unwrap(id) >= storedDistributors.length) return address(0);
        address ownership = ownerships[id];
        return ownership == address(0) ? owner() : ownership;
    }

    /// @inheritdoc IMerkleDistributorPeriphery
    function areClaimed(
        Query[] calldata queries
    ) external view override returns (bool[] memory results) {
        uint256 length = queries.length;
        results = new bool[](length);
        for (uint256 i; i < length; ) {
            Query memory query = queries[i];
            results[i] = query.distributor.isClaimed(Index.unwrap(query.index));

            unchecked {
                i++;
            }
        }
    }

    function queued()
        external
        view
        override
        returns (
            address deployer,
            IERC20 token,
            Amount totalAmount,
            MerkleRoot merkleRoot,
            Timestamp endTime
        )
    {
        deployer = queuedDeployer;
        token = queuedToken;
        totalAmount = queuedTotalAmount;
        merkleRoot = queuedMerkleRoot;
        endTime = queuedEndTime;
    }

    function queue(
        address deployer,
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    ) external override onlyOwner {
        if (deployer == address(0))
            revert CannotQueueWithDeployerAsZeroAddress();
        if (address(token) == address(0))
            revert CannotQueueWithTokenAsZeroAddress();
        if (totalAmount.isZero()) revert CannotQueueWithTotalAmountAsZero();
        if (endTime <= Timestamp.wrap(block.timestamp))
            revert CannotQueueWithEndTimeInThePast();

        queuedDeployer = deployer;
        queuedToken = token;
        queuedTotalAmount = totalAmount;
        queuedMerkleRoot = merkleRoot;
        queuedEndTime = endTime;

        emit Queue(deployer, token, totalAmount, merkleRoot, endTime);
    }

    function unqueue() external override onlyOwner {
        queuedDeployer = address(0);
        queuedToken = IERC20(address(0));
        queuedTotalAmount = Amount.wrap(0);
        queuedMerkleRoot = MerkleRoot.wrap(0);
        queuedEndTime = Timestamp.wrap(0);

        emit Unqueue();
    }

    function createFromQueue(
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    )
        external
        override
        returns (Id id, IMerkleDistributorWithDeadline distributor)
    {
        if (msg.sender != queuedDeployer)
            revert OnlyAuthorizedOwner(msg.sender);
        if (address(token) != address(queuedToken))
            revert InconsistentQueuedToken();
        if (Amount.unwrap(totalAmount) != Amount.unwrap(queuedTotalAmount))
            revert InconsistentQueuedTotalAmount();
        if (MerkleRoot.unwrap(merkleRoot) != MerkleRoot.unwrap(merkleRoot))
            revert InconsistentQueuedMerkleRoot();
        if (Timestamp.unwrap(endTime) != Timestamp.unwrap(queuedEndTime))
            revert InconsistentQueuedEndTime();
        if (queuedEndTime <= Timestamp.wrap(block.timestamp))
            revert CannotCreateWithEndTimeInThePast();

        id = Id.wrap(storedDistributors.length);
        distributor = IMerkleDistributorWithDeadline(
            Create2.deploy(
                0,
                bytes32(Id.unwrap(id)),
                abi.encodePacked(
                    type(MerkleDistributorWithDeadline).creationCode,
                    abi.encode(
                        address(token),
                        MerkleRoot.unwrap(merkleRoot),
                        Timestamp.unwrap(endTime)
                    )
                )
            )
        );
        storedDistributors.push(distributor);
        queuedDeployer = address(0);
        queuedToken = IERC20(address(0));
        queuedTotalAmount = Amount.wrap(0);
        queuedMerkleRoot = MerkleRoot.wrap(0);
        queuedEndTime = Timestamp.wrap(0);
        ownerships[id] = msg.sender;
        ownershipGivenDistributors[distributor] = msg.sender;
        token.safeTransferFrom(
            msg.sender,
            address(distributor),
            Amount.unwrap(totalAmount)
        );
        emit CreateFromQueue(
            id,
            distributor,
            token,
            totalAmount,
            merkleRoot,
            endTime
        );
    }

    /// @inheritdoc IMerkleDistributorPeriphery
    function create(
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    )
        external
        override
        onlyOwner
        returns (Id id, IMerkleDistributorWithDeadline distributor)
    {
        if (address(token) == address(0))
            revert CannotCreateWithTokenAsZeroAddress();
        if (totalAmount.isZero()) revert CannotCreateWithTotalAmountAsZero();
        if (endTime <= Timestamp.wrap(block.timestamp))
            revert CannotCreateWithEndTimeInThePast();

        id = Id.wrap(storedDistributors.length);
        distributor = IMerkleDistributorWithDeadline(
            Create2.deploy(
                0,
                bytes32(Id.unwrap(id)),
                abi.encodePacked(
                    type(MerkleDistributorWithDeadline).creationCode,
                    abi.encode(
                        address(token),
                        MerkleRoot.unwrap(merkleRoot),
                        Timestamp.unwrap(endTime)
                    )
                )
            )
        );
        storedDistributors.push(distributor);
        token.safeTransferFrom(
            msg.sender,
            address(distributor),
            Amount.unwrap(totalAmount)
        );
        emit Create(id, distributor, token, totalAmount, merkleRoot, endTime);
    }

    /// @inheritdoc IMerkleDistributorPeriphery
    function claim(Order[] calldata orders) external override {
        uint256 length = orders.length;
        for (uint256 i; i < length; ) {
            Order memory order = orders[i];

            MerkleProof[] memory merkleProof = order.merkleProof;
            uint256 lengthOfMerkleProof = merkleProof.length;
            bytes32[] memory merkleProofInBytes32 = new bytes32[](
                lengthOfMerkleProof
            );
            for (uint256 j; j < lengthOfMerkleProof; ) {
                merkleProofInBytes32[j] = MerkleProof.unwrap(merkleProof[j]);
                unchecked {
                    j++;
                }
            }

            order.distributor.claim(
                Index.unwrap(order.index),
                msg.sender,
                Amount.unwrap(order.amount),
                merkleProofInBytes32
            );

            unchecked {
                i++;
            }
        }
    }

    /// @inheritdoc IMerkleDistributorPeriphery
    function withdraw(
        IMerkleDistributorWithDeadline[] calldata distributors
    ) external override returns (Amount[] memory amounts) {
        uint256 length = distributors.length;
        amounts = new Amount[](length);
        for (uint256 i; i < length; ) {
            IMerkleDistributorWithDeadline distributor = distributors[i];
            address deployer = ownershipGivenDistributors[distributor];
            if (deployer == address(0)) deployer = owner();
            if (deployer != msg.sender) revert OnlyAuthorizedOwner(msg.sender);
            IERC20 token = IERC20(distributor.token());
            Amount amount = Amount.wrap(token.balanceOf(address(distributor)));

            if (!amount.isZero()) {
                amounts[i] = amount;
                distributor.withdraw();
                token.safeTransfer(msg.sender, Amount.unwrap(amount));
                emit Withdraw(distributor, amount);
            }

            unchecked {
                i++;
            }
        }
    }

    /// @inheritdoc IOwnable2Step
    function owner()
        public
        view
        override(Ownable, IOwnable2Step)
        returns (address)
    {
        return super.owner();
    }

    /// @inheritdoc IOwnable2Step
    function renounceOwnership() public override(Ownable, IOwnable2Step) {
        super.renounceOwnership();
    }

    /// @inheritdoc IOwnable2Step
    function pendingOwner()
        public
        view
        override(Ownable2Step, IOwnable2Step)
        returns (address)
    {
        return super.pendingOwner();
    }

    /// @inheritdoc IOwnable2Step
    function transferOwnership(
        address newOwner
    ) public override(Ownable2Step, IOwnable2Step) {
        super.transferOwnership(newOwner);
    }

    /// @inheritdoc IOwnable2Step
    function acceptOwnership() public override(Ownable2Step, IOwnable2Step) {
        super.acceptOwnership();
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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/utils/SafeERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 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: lib/openzeppelin-contracts/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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: lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev Not enough balance for performing a CREATE2 deploy.
     */
    error Create2InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev The deployment failed.
     */
    error Create2FailedDeployment();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Create2InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        /// @solidity memory-safe-assembly
        assembly {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
        }
        if (addr == address(0)) {
            revert Create2FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := keccak256(start, 85)
        }
    }
}

// File: src/uniswap/interfaces/IMerkleDistributorWithDeadline.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.20;

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

interface IMerkleDistributorWithDeadline is IMerkleDistributor {
    function owner() external view returns (address);

    function endTime() external returns (uint256);

    function withdraw() external;
}

// File: src/uniswap/MerkleDistributorWithDeadline.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.20;

import {MerkleDistributor} from "./MerkleDistributor.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

error EndTimeInPast();
error ClaimWindowFinished();
error NoWithdrawDuringClaim();

contract MerkleDistributorWithDeadline is MerkleDistributor, Ownable {
    using SafeERC20 for IERC20;

    uint256 public immutable endTime;

    constructor(
        address token_,
        bytes32 merkleRoot_,
        uint256 endTime_
    ) MerkleDistributor(token_, merkleRoot_) Ownable(msg.sender) {
        if (endTime_ <= block.timestamp) revert EndTimeInPast();
        endTime = endTime_;
    }

    function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) public override {
        if (block.timestamp > endTime) revert ClaimWindowFinished();
        super.claim(index, account, amount, merkleProof);
    }

    function withdraw() external onlyOwner {
        if (block.timestamp < endTime) revert NoWithdrawDuringClaim();
        IERC20(token).safeTransfer(
            msg.sender,
            IERC20(token).balanceOf(address(this))
        );
    }
}

// File: src/interfaces/IMerkleDistributorPeriphery.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IMerkleDistributorWithDeadline} from "src/uniswap/interfaces/IMerkleDistributorWithDeadline.sol";
import {IOwnable2Step} from "src/interfaces/IOwnable2Step.sol";
import {Amount} from "src/types/Amount.sol";
import {Id} from "src/types/Id.sol";
import {Index} from "src/types/Index.sol";
import {MerkleProof} from "src/types/MerkleProof.sol";
import {MerkleRoot} from "src/types/MerkleRoot.sol";
import {Number} from "src/types/Number.sol";
import {Timestamp} from "src/types/Timestamp.sol";

/// @title A periphery contract to deploy and manage Merkle Distributor contracts.
/// @author Timelord
/// @dev The owner can deploy new Merkle Distributor contracts and token transfer.
/// @dev Users can claim rewards from multiple Distributor contracts.
interface IMerkleDistributorPeriphery is IOwnable2Step {
    event Queue(
        address indexed deployer,
        IERC20 indexed token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    );
    
    event Unqueue();

    event CreateFromQueue(
        Id indexed id,
        IMerkleDistributorWithDeadline indexed distributor,
        IERC20 indexed token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    );

    /// @dev Event emitted when the contract deploys a new distributor contract.
    /// @notice Only emitted once for a unique id and distributor address.
    /// @param id The id of the newly deployed distributor contract. Id increments per new deployment.
    /// The id parameter is indexed, to query for one specific event for the given id.
    /// @param distributor The address of the newly deployed distributor contract.
    /// The distributor parameter is indexed, to query for one specific event for the given address.
    /// @param token The address of the ERC20 token reward.
    /// The token parameter is indexed, to query for all events where the reward is the ERC20 token.
    /// @param totalAmount The total amount of ERC20 token reward.
    /// @param merkleRoot The 32 bytes merkle root.
    /// @param endTime The deadline for claiming the rewards in unix timestamp.
    event Create(
        Id indexed id,
        IMerkleDistributorWithDeadline indexed distributor,
        IERC20 indexed token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    );

    /// @dev Event emitted when the owner withdraws the remaining rewards after deadline from a distributor contract.
    /// @notice Only emitted once for a unique id and distributor address.
    /// @param distributor The address of the distributor contract where the rewards were withdrawn.
    /// The distributor parameter is indexed, to query for one specific withdraw event for the given address.
    /// @param amount The amount of remaining ERC20 token reward withdrawn.
    event Withdraw(
        IMerkleDistributorWithDeadline indexed distributor,
        Amount amount
    );

    error CannotQueueWithDeployerAsZeroAddress();

    error CannotQueueWithTokenAsZeroAddress();

    error CannotQueueWithTotalAmountAsZero();

    error CannotQueueWithEndTimeInThePast();

    /// @dev Reverts with this error when calling the create function with token address as zero.
    error CannotCreateWithTokenAsZeroAddress();

    /// @dev Reverts with this error when calling the create function with total amount as zero.
    error CannotCreateWithTotalAmountAsZero();

    /// @dev Reverts with this error when calling the create function with end time in the past.
    error CannotCreateWithEndTimeInThePast();

    error OnlyAuthorizedOwner(address account);

    error InconsistentQueuedToken();

    error InconsistentQueuedTotalAmount();

    error InconsistentQueuedMerkleRoot();

    error InconsistentQueuedEndTime();

    /// @dev View the address given the id of the distributor contract.
    /// @notice Returns the zero address if the distributor contract is not deployed given the id.
    /// @param id The id of the distributor address.
    function merkleDistributor(
        Id id
    ) external view returns (IMerkleDistributorWithDeadline);

    /// @dev View the total number of distributor contracts deployed.
    function totalMerkleDistributors() external view returns (Number);

    function ownerGivenId(Id id) external view returns (address);

    /// @dev A record of a single query for areClaimed function call.
    /// @param distributor The address of the distributor contract being queried.
    /// @param index The index of the mapping of user address and reward amount where the merkle root is generated from.
    struct Query {
        IMerkleDistributorWithDeadline distributor;
        Index index;
    }

    /// @dev Checks that the reward from multiple distributors are claimed given the index of the mapping.
    /// @param queries The list of queries.
    /// @return results The list of boolean results.
    /// @notice The length of queries and results will always be equal.
    /// @notice When the query distributor address does not exist or the index does not exist for the mapping, it will return false.
    function areClaimed(
        Query[] calldata queries
    ) external view returns (bool[] memory results);

    function queued()
        external
        view
        returns (
            address deployer,
            IERC20 token,
            Amount totalAmount,
            MerkleRoot merkleRoot,
            Timestamp endTime
        );

    function queue(
        address deployer,
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    ) external;

    function unqueue() external;

    function createFromQueue(
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    ) external returns (Id id, IMerkleDistributorWithDeadline distributor);

    /// @dev The owner deploys a new distributor contract and transfer the necessary ERC20 tokens to the distributor.
    /// @dev Emits the Create event.
    /// @notice Can only be called by the owner.
    /// @notice The owner must first approve this contract for token transfer.
    /// @param token The address of the ERC20 token reward.
    /// Reverts when the address does not follow ERC20 standard or is zero address.
    /// Reverts with CannotCreateWithTokenAsZeroAddress when the address is the zero address.
    /// @param totalAmount The total amount of ERC20 token reward being distributed.
    /// Reverts when there is not enough ERC20 token from the owner.
    /// Reverts with CannotCreateWithTotalAmountAsZero when the totalAmount is zero.
    /// @param merkleRoot The merkle root of the distributor contract for claiming verification.
    /// @param endTime The deadline of distribution and claim in unix timestamp.
    /// Reverts when with CannotCreateWithEndTimeInThePast the block timestamp is greater than the endTime on function call.
    /// @return id The id of the newly deployed distributor contract.
    /// @return distributor The address of the newly deployed distributor contract.
    function create(
        IERC20 token,
        Amount totalAmount,
        MerkleRoot merkleRoot,
        Timestamp endTime
    ) external returns (Id id, IMerkleDistributorWithDeadline distributor);

    /// @dev A record of a single order for claim function call.
    /// @param distributor The address of the distributor contract being claimed.
    /// @param index The index of the mapping of user address and reward amount where the merkle root is generated from.
    /// @param amount The exact amount of ERC20 token address being claimed.
    /// @notice The amount must be exactly equal to the mapping of user address and reward amount.
    /// @param merkleProof A list of 32 bytes for verification of claiming.
    struct Order {
        IMerkleDistributorWithDeadline distributor;
        Index index;
        Amount amount;
        MerkleProof[] merkleProof;
    }

    /// @dev Msg sender claims the rewards given the list of orders.
    /// @dev Transfer the ERC20 token rewards to the correct user.
    /// @notice Reverts when any of the orders fail.
    /// @notice User can alternatively claim the rewards directly from the individual distributor contracts, but one at a time.
    /// @notice The contract does not emit any events. Instead the individual distributor contracts will emit the Claimed events.
    /// Ingest these events by getting the address of the deployed distributor contracts from the Create event.
    /// @param orders The list of orders for claiming.
    function claim(Order[] calldata orders) external;

    /// @dev The owner withdraws the remaining rewards of the distributor contracts.
    /// @dev Can only be called when the distributor contracts have reached the endTime deadline.
    /// @dev Emits Withdraw event for each withdrawal of distributor contracts.
    /// @notice Reverts when any withdrawals fail.
    /// @param distributors The list of distributor addresses to withdraw from.
    /// @return amounts The list of amount of token withdrawn.
    /// Distributors and amounts will always have the same length.
    /// The token denomination of amount is the token reward of the distributor of the same index.
    function withdraw(
        IMerkleDistributorWithDeadline[] calldata distributors
    ) external returns (Amount[] memory amounts);
}

// File: src/interfaces/IOwnable2Step.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @title Interface for Two Step Ownable contract
/// @author Timelord
interface IOwnable2Step {
    /// @dev Returns the address of the owner.
    /// @notice Returns zero when there is no owner.
    function owner() external view returns (address);

    /// @dev Returns the address of the pending owner.
    /// @notice Returns zero when there is no pending owner.
    function pendingOwner() external view returns (address);

    /// @dev Renounce ownership of the contract.
    /// @dev Can only be called by the owner.
    /// @dev The address of the owner will change to zero address.
    /// @notice This transaction cannot be reversed.
    /// @notice Owner must make sure any remaining rewards in the distributors are all withdrawn.
    function renounceOwnership() external;

    /// @dev Transfer the ownership to another address.
    /// @dev Can only be called by the owner.
    /// @dev Does not transfer the ownership immediately, instead set the pending owner first.
    /// @notice The pending owner must accept ownership to finalize the ownership transfer.
    /// @param newOwner The address of the new pending owner.
    function transferOwnership(address newOwner) external;

    /// @dev The new pending owner accepts the ownership transfer.
    /// @notice Can only be called by the pending owner.
    function acceptOwnership() external;
}

// File: src/types/Amount.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Represents the ERC20 token amount.
type Amount is uint256;
using {add as +, sub as -, isZero} for Amount global;

function add(Amount a, Amount b) pure returns (Amount sum) {
    sum = Amount.wrap(Amount.unwrap(a) + Amount.unwrap(b));
}

function sub(Amount a, Amount b) pure returns (Amount difference) {
    difference = Amount.wrap(Amount.unwrap(a) - Amount.unwrap(b));
}

function isZero(Amount a) pure returns (bool result) {
    result = Amount.unwrap(a) == 0;
}
// File: src/types/Id.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Unique id for identification.
type Id is uint256;

// File: src/types/Index.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev The index for mapping of user addresses and reward amount.
type Index is uint256;

// File: src/types/MerkleProof.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Represents the individual 32 bytes from a merkle proof of the distributor contract.
type MerkleProof is bytes32;

// File: src/types/MerkleRoot.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Represents the 32 bytes merkle root of the distributor contract.
type MerkleRoot is bytes32;

// File: src/types/Number.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Represents the number of contracts.
type Number is uint256;

// File: src/types/Timestamp.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.20;

/// @dev Represents the number of seconds in unix timestamp.
type Timestamp is uint256;
using {add as +, sub as -, equal as ==, notEqual as !=, lessThan as <, lessThanOrEqual as <=, greaterThan as >, greaterThanOrEqual as >=} for Timestamp global;

function add(Timestamp a, Timestamp b) pure returns (Timestamp sum) {
    sum = Timestamp.wrap(Timestamp.unwrap(a) + Timestamp.unwrap(b));
}

function sub(Timestamp a, Timestamp b) pure returns (Timestamp difference) {
    difference = Timestamp.wrap(Timestamp.unwrap(a) - Timestamp.unwrap(b));
}

function equal(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) == Timestamp.unwrap(b);
}

function notEqual(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) != Timestamp.unwrap(b);
}

function lessThan(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) < Timestamp.unwrap(b);
}

function lessThanOrEqual(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) <= Timestamp.unwrap(b);
}

function greaterThan(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) > Timestamp.unwrap(b);
}

function greaterThanOrEqual(Timestamp a, Timestamp b) pure returns (bool result) {
    result = Timestamp.unwrap(a) >= Timestamp.unwrap(b);
}
// File: lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
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].
     *
     * CAUTION: See Security Considerations above.
     */
    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/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (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: src/uniswap/interfaces/IMerkleDistributor.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.20;

// Allows anyone to claim a token if they exist in a merkle root.
interface IMerkleDistributor {
    // Returns the address of the token distributed by this contract.
    function token() external view returns (address);

    // Returns the merkle root of the merkle tree containing account balances available to claim.
    function merkleRoot() external view returns (bytes32);

    // Returns true if the index has been marked claimed.
    function isClaimed(uint256 index) external view returns (bool);

    // Claim the given amount of the token to the given address. Reverts if the inputs are invalid.
    function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external;

    // This event is triggered whenever a call to #claim succeeds.
    event Claimed(uint256 index, address account, uint256 amount);
}

// File: src/uniswap/MerkleDistributor.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.20;

import {IERC20, SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {MerkleProof} from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import {IMerkleDistributor} from "./interfaces/IMerkleDistributor.sol";

error AlreadyClaimed();
error InvalidProof();

contract MerkleDistributor is IMerkleDistributor {
    using SafeERC20 for IERC20;

    address public immutable override token;
    bytes32 public immutable override merkleRoot;

    // This is a packed array of booleans.
    mapping(uint256 => uint256) private claimedBitMap;

    constructor(address token_, bytes32 merkleRoot_) {
        token = token_;
        merkleRoot = merkleRoot_;
    }

    function isClaimed(uint256 index) public view override returns (bool) {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        uint256 claimedWord = claimedBitMap[claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        return claimedWord & mask == mask;
    }

    function _setClaimed(uint256 index) private {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        claimedBitMap[claimedWordIndex] =
            claimedBitMap[claimedWordIndex] |
            (1 << claimedBitIndex);
    }

    function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) public virtual override {
        if (isClaimed(index)) revert AlreadyClaimed();

        // Verify the merkle proof.
        bytes32 node = keccak256(abi.encodePacked(index, account, amount));
        if (!MerkleProof.verify(merkleProof, merkleRoot, node))
            revert InvalidProof();

        // Mark it claimed and send the token.
        _setClaimed(index);
        IERC20(token).safeTransfer(account, amount);

        emit Claimed(index, account, amount);
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.20;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            if (proofPos != proofLen) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            if (proofPos != proofLen) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Sorts the pair (a, b) and hashes the result.
     */
    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}