// File: contracts/distributor/DistributorV2.sol



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

/*
The DistributorV2 is a clone contract version of the Uniswap Distributor. 
It expects a merkleRoot and a manifest, which is a public IPFS CID where to find
the full merkle tree.
*/

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "../initializable/Initializable.sol";
import "../interfaces/IDistributorV2.sol";

contract DistributorV2 is Initializable, IDistributorV2 {
    address public override token;
    bytes32 public override merkleRoot;
    string public manifest;
    // This is a packed array of booleans.
    mapping(uint256 => uint256) private claimedBitMap;
    // The returnTokenAddress is here for safety purposes. Its intended use is 
    // only when something goes wrong with the distributor and we need to redo
    // it. If this has a primary funder (like with the Amaranth Prize), then it
    // is set to that funder. Otherwise, it is set to the gnosis address for the
    // RP org. This is sacrificing some web3 purity for the sake of user
    // experience, where user is both funder and recipient.
    address public returnTokenAddress;

    // The researchPortfolioGnosis should be one of the following:
    // Goerli: 0x6f07856f4974A32a54A0A0045eDfAEd97Cc78136
    // Mainnet: 0xAAbF8DC8c8208e023c5D8e2d0e3dd30415559E0E
    address public researchPortfolioGnosis;

    error InvalidSenderAddress();
    error MerkleRootIsZero();
    error ManifestIsEmpty();
    error NullReturnTokenAddress();
    error TokenIsSet();
    error TokenIsZeroAddress();
    error AlreadyClaimed();
    error InvalidProof();
    error TransferFailed();
    
    function initialize(
        bytes32 merkleRoot_,
        string memory manifest_,
        address returnTokenAddress_,
        address researchPortfolioGnosis_
    ) public virtual initializer {
        if (merkleRoot_ == bytes32(0)) {
            revert MerkleRootIsZero();
        }
        if (bytes(manifest_).length == 0) {
            revert ManifestIsEmpty();
        }        
        __DistributorV2_init(merkleRoot_, manifest_, returnTokenAddress_, 
            researchPortfolioGnosis_);
    }

    function __DistributorV2_init(
        bytes32 merkleRoot_,
        string memory manifest_,
        address returnTokenAddress_,
        address researchPortfolioGnosis_
    ) internal initializer {
        // Do the chain of initializations here.
        __DistributorV2_init_unchained(merkleRoot_, manifest_, 
            returnTokenAddress_, researchPortfolioGnosis_);
    }

    function __DistributorV2_init_unchained(
        bytes32 merkleRoot_,
        string memory manifest_,
        address returnTokenAddress_,
        address researchPortfolioGnosis_
    ) internal initializer {
        merkleRoot = merkleRoot_;
        manifest = manifest_;
        returnTokenAddress = returnTokenAddress_;
        researchPortfolioGnosis = researchPortfolioGnosis_;
    }

    function setToken(address token_) external override {
        if (token != address(0x0)) {
            revert TokenIsSet();
        }
        if (token_ == address(0x0)) {
            revert TokenIsZeroAddress();
        }
        token = token_;
        emit ERC20Distribution(token_, DistributorTypeMerkleV2);
    }

    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 claimForAccount(
        uint256 index,
        uint256 amount,
        address account,
        bytes32[] calldata merkleProof
    ) public override {
        if (isClaimed(index)) {
            revert AlreadyClaimed();
        }

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

        // Mark it claimed and send the token.
        _setClaimed(index);
        if (!IERC20(token).transfer(account, amount)) {
            revert TransferFailed();
        }
        emit ERC20ClaimForAccount(index, amount, account);
    }

    /*
    Withdraw can only be called by a Research Portfolio gnosis address, which
    are hardcoded at the top. This is in order to limit the action just to the 
    trusted organization. Note that if the returnTokenAddress is not set, then
    this function doesn't actually do anything. Otherwise, the withdrawn token 
    goes to the returnTokenAddress. 
    
    This is just meant to be used in case of there being a problem with this 
    distributor.
    */
    function withdraw() external {
        if (returnTokenAddress == address(0x0)) {
            revert NullReturnTokenAddress();
        }
        if (msg.sender != researchPortfolioGnosis) {
            revert InvalidSenderAddress();
        }        

        uint256 balance = IERC20(token).balanceOf(address(this));
        if (!IERC20(token).transfer(returnTokenAddress, balance)) {
            revert TransferFailed();
        }
    }

    /*
    withdrawBalance is similar to above. The difference is that the caller can 
    specify an amount to transfer.
    
    This is just meant to be used in case of there being a problem with this 
    distributor and we want to return a small amount. An example is if the email
    embedded wallet is unreachable.
    */
    function withdrawAmount(uint256 amount) external {
        if (returnTokenAddress == address(0x0)) {
            revert NullReturnTokenAddress();
        }
        if (msg.sender != researchPortfolioGnosis) {
            revert InvalidSenderAddress();
        }        
        
        if (!IERC20(token).transfer(returnTokenAddress, amount)) {
            revert TransferFailed();
        }
    }    
}

// File: contracts/initializable/Initializable.sol



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

/*
  This was taken from OpenZeppelin.
*/

/**
 * @title Initializable
 *
 * @dev Helper contract to support initializer functions. To use it, replace
 * the constructor with a function that has the `initializer` modifier.
 * WARNING: Unlike constructors, initializer functions must be manually
 * invoked. This applies both to deploying an Initializable contract, as well
 * as extending an Initializable contract via inheritance.
 * WARNING: When used with inheritance, manual care must be taken to not invoke
 * a parent initializer twice, or ensure that all initializers are idempotent,
 * because this is not dealt with automatically as with constructors.
 */
contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private initialized;
    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private initializing;

    error AlreadyInitialized();

    /**
     * @dev Modifier to use in the initializer function of a contract.
     */
    modifier initializer() {
        if (!(initializing || isConstructor() || !initialized)) {
            revert AlreadyInitialized();
        }

        bool isTopLevelCall = !initializing;
        if (isTopLevelCall) {
            initializing = true;
            initialized = true;
        }

        _;

        if (isTopLevelCall) {
            initializing = false;
        }
    }

    /// @dev Returns true if and only if the function is running in the constructor
    function isConstructor() private view returns (bool) {
        // extcodesize checks the size of the code stored in an address, and
        // address returns the current address. Since the code is still not
        // deployed when running a constructor, any checks on its code size will
        // yield zero, making it an effective way to detect if a contract is
        // under construction or not.
        address self = address(this);
        uint256 cs;
        assembly {
            cs := extcodesize(self)
        }
        return cs == 0;
    }
}

// File: contracts/interfaces/IDistributorV2.sol



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

/*
    Interface for the DistributorV2. Adds two functions onto IERC20Distributor.
*/

import "./IERC20Distributor.sol";

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

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

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



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

    /**
     * @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);
}

// File: @openzeppelin/contracts/utils/cryptography/MerkleProof.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Trees proofs.
 *
 * The proofs can be generated using the JavaScript library
 * https://github.com/miguelmota/merkletreejs[merkletreejs].
 * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
 *
 * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
 */
library MerkleProof {
    /**
     * @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) {
        bytes32 computedHash = leaf;

        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 proofElement = proof[i];

            if (computedHash <= proofElement) {
                // Hash(current computed hash + current element of the proof)
                computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
            } else {
                // Hash(current element of the proof + current computed hash)
                computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
            }
        }

        // Check if the computed hash (root) is equal to the provided root
        return computedHash == root;
    }
}

// File: contracts/interfaces/IERC20Distributor.sol



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

/*
    Interface for a Distributor of ERC20 tokens.
*/

// The DistributorTypeMerkleV# constants identify the Distributor type. These
// are used when emitting events.
uint256 constant DistributorTypeMerkleV1 = 1;
uint256 constant DistributorTypeMerkleV2 = 2;

// Allows anyone to claim a token if they haven't already.
interface IERC20Distributor {
    // Returns the address of the token distributed by this contract.
    function token() external view returns (address);

    function manifest() external view returns (string memory);

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

    // Sets the token for the Distributor. Aborts if already done.
    function setToken(address token_) external;

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

    // This event is triggered when the (first) call to #distribute succeeds.
    event ERC20Distribution(address token, uint256 distributorType);
}