// File: contracts/p2pEth2Depositor/P2pEth2Depositor.sol



// SPDX-FileCopyrightText: 2023 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "../@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "../@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "./interfaces/IDepositContract.sol";
import "../feeDistributorFactory/IFeeDistributorFactory.sol";
import "./IP2pEth2Depositor.sol";

/**
* @notice Should be a FeeDistributorFactory contract
* @param _passedAddress passed address that does not support IFeeDistributorFactory interface
*/
error P2pEth2Depositor__NotFactory(address _passedAddress);

/**
* @notice do not send ETH directly here
*/
error P2pEth2Depositor__DoNotSendEthDirectlyHere();

/**
* @notice you can deposit only 1 to 400 validators per transaction
*/
error P2pEth2Depositor__ValidatorCountError();

/**
* @notice the amount of ETH does not match the amount of validators
*/
error P2pEth2Depositor__EtherValueError();

/**
* @notice amount of parameters do no match
*/
error P2pEth2Depositor__AmountOfParametersError();

/**
* @title Batch deposit contract
* @dev Makes up to 400 ETH2 DepositContract calls within 1 transaction
* @dev Create a FeeDistributor instance (1 for batch)
*/
contract P2pEth2Depositor is ERC165, IP2pEth2Depositor {
    
    /**
    * @dev 400 deposits (12800 ETH) is determined by calldata size limit of 128 kb
    * @dev https://ethereum.stackexchange.com/questions/144120/maximum-calldata-size-per-block
    */
    uint256 public constant validatorsMaxAmount = 400;

    /**
     * @dev Collateral size of one node.
     */
    uint256 public constant collateral = 32 ether;

    /**
     * @dev Eth2 Deposit Contract address.
     */
    IDepositContract public immutable i_depositContract;

    /**
    * @dev Factory for cloning (EIP-1167) FeeDistributor instances pre client
    */
    IFeeDistributorFactory public immutable i_feeDistributorFactory;

    /**
     * @dev Setting Eth2 Smart Contract address during construction.
     */
    constructor(bool _mainnet, address _depositContract, address _feeDistributorFactory) {
        if (!ERC165Checker.supportsInterface(_feeDistributorFactory, type(IFeeDistributorFactory).interfaceId)) {
            revert P2pEth2Depositor__NotFactory(_feeDistributorFactory);
        }

        i_depositContract = _mainnet
        ? IDepositContract(0x00000000219ab540356cBB839Cbe05303d7705Fa) // real Mainnet DepositContract
        : (_depositContract == 0x0000000000000000000000000000000000000000)
            ? IDepositContract(0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b) // real Goerli DepositContract
            : IDepositContract(_depositContract);

        i_feeDistributorFactory = IFeeDistributorFactory(_feeDistributorFactory);
    }

    /**
     * @dev This contract will not accept direct ETH transactions.
     */
    receive() external payable {
        revert P2pEth2Depositor__DoNotSendEthDirectlyHere();
    }

    /**
    * @notice Function that allows to deposit up to 400 validators at once.
    *
    * @dev In _clientConfig, recipient is mandatory.
    * @dev In _clientConfig, basisPoints can be 0, defaultClientBasisPoints from FeeDistributorFactory will be applied in that case.
    * @dev In _referrerConfig, both recipient and basisPoints can be 0 if there is no referrer.
    *
    * @param _pubkeys Array of BLS12-381 public keys.
    * @param _withdrawal_credentials Commitment to a public keys for withdrawals. 1, same for all
    * @param _signatures Array of BLS12-381 signatures.
    * @param _deposit_data_roots Array of the SHA-256 hashes of the SSZ-encoded DepositData objects.
    * @param _clientConfig Address and basis points (percent * 100) of the client
    * @param _referrerConfig Address and basis points (percent * 100) of the referrer.
    */
    function deposit(
        bytes[] calldata _pubkeys,
        bytes calldata _withdrawal_credentials, // 1, same for all
        bytes[] calldata _signatures,
        bytes32[] calldata _deposit_data_roots,
        IFeeDistributor.FeeRecipient calldata _clientConfig,
        IFeeDistributor.FeeRecipient calldata _referrerConfig
    ) external payable {

        uint256 validatorCount = _pubkeys.length;

        if (validatorCount == 0 || validatorCount > validatorsMaxAmount) {
            revert P2pEth2Depositor__ValidatorCountError();
        }

        if (msg.value != collateral * validatorCount) {
            revert P2pEth2Depositor__EtherValueError();
        }

        if (!(
            _signatures.length == validatorCount &&
            _deposit_data_roots.length == validatorCount
        )) {
            revert P2pEth2Depositor__AmountOfParametersError();
        }

        uint64 firstValidatorId = toUint64(i_depositContract.get_deposit_count()) + 1;

        for (uint256 i = 0; i < validatorCount;) {
            // pubkey, withdrawal_credentials, signature lengths are already checked inside ETH DepositContract

            i_depositContract.deposit{value : collateral}(
                _pubkeys[i],
                _withdrawal_credentials,
                _signatures[i],
                _deposit_data_roots[i]
            );

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        // First, make sure all the deposits are successful, then deploy FeeDistributor
        address newFeeDistributorAddress = i_feeDistributorFactory.createFeeDistributor(
            _clientConfig,
            _referrerConfig,
            IFeeDistributor.ValidatorData({
                clientOnlyClRewards : 0,
                firstValidatorId : firstValidatorId,
                validatorCount : uint16(validatorCount)
            })
        );

        emit P2pEth2DepositEvent(msg.sender, newFeeDistributorAddress, firstValidatorId, validatorCount);
    }

    /**
    * @dev See {IERC165-supportsInterface}.
    */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IP2pEth2Depositor).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
    * @dev Convert deposit_count from ETH2 DepositContract to uint64
    * ETH2 DepositContract returns inverted bytes. Need to invert them back.
    */
    function toUint64(bytes memory b) internal pure returns (uint64) {
        uint64 result;
        assembly {
            let x := mload(add(b, 8))

            result := or(
                or (
                    or(
                        and(0xff, shr(56, x)),
                        and(0xff00, shr(40, x))
                    ),
                    or(
                        and(0xff0000, shr(24, x)),
                        and(0xff000000, shr(8, x))
                    )
                ),

                or (
                    or(
                        and(0xff00000000, shl(8, x)),
                        and(0xff0000000000, shl(24, x))
                    ),
                    or(
                        and(0xff000000000000, shl(40, x)),
                        and(0xff00000000000000, shl(56, x))
                    )
                )
            )
        }
        return result;
    }
}

// File: contracts/@openzeppelin/contracts/utils/introspection/ERC165Checker.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.2) (utils/introspection/ERC165Checker.sol)

pragma solidity 0.8.10;

import "./IERC165.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface,
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            _supportsERC165Interface(account, type(IERC165).interfaceId) &&
            !_supportsERC165Interface(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && _supportsERC165Interface(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
        internal
        view
        returns (bool[] memory)
    {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = _supportsERC165Interface(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in _interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!_supportsERC165Interface(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     * Interface identification is specified in ERC-165.
     */
    function _supportsERC165Interface(address account, bytes4 interfaceId) private view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

// File: contracts/@openzeppelin/contracts/utils/introspection/ERC165.sol



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

pragma solidity 0.8.10;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: contracts/p2pEth2Depositor/interfaces/IDepositContract.sol



// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.10;

// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
    /// @notice A processed deposit event.
    event DepositEvent(
        bytes pubkey,
        bytes withdrawal_credentials,
        bytes amount,
        bytes signature,
        bytes index
    );

    /// @notice Submit a Phase 0 DepositData object.
    /// @param pubkey A BLS12-381 public key.
    /// @param withdrawal_credentials Commitment to a public key for withdrawals.
    /// @param signature A BLS12-381 signature.
    /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
    /// Used as a protection against malformed input.
    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable;

    /// @notice Query the current deposit root hash.
    /// @return The deposit root hash.
    function get_deposit_root() external view returns (bytes32);

    /// @notice Query the current deposit count.
    /// @return The deposit count encoded as a little endian 64-bit number.
    function get_deposit_count() external view returns (bytes memory);
}

// File: contracts/feeDistributorFactory/IFeeDistributorFactory.sol



// SPDX-FileCopyrightText: 2023 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "../@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../access/IOwnable.sol";
import "../feeDistributor/IFeeDistributor.sol";

/**
 * @dev External interface of FeeDistributorFactory declared to support ERC165 detection.
 */
interface IFeeDistributorFactory is IOwnable, IERC165 {
    // Events

    /**
    * @notice Emits when a new FeeDistributor instance has been created for a client
    * @param _newFeeDistributorAddress address of the newly created FeeDistributor contract instance
    * @param _clientAddress address of the client for whom the new instance was created
    */
    event FeeDistributorCreated(address indexed _newFeeDistributorAddress, address indexed _clientAddress);

    /**
    * @notice Emits when a new FeeDistributor contract address has been set as a reference instance.
    * @param _referenceFeeDistributor the address of the new reference implementation contract
    */
    event ReferenceInstanceSet(address indexed _referenceFeeDistributor);

    /**
    * @notice Emits when a new P2pEth2Depositor contract address has been set.
    * @param _p2pEth2Depositor the address of the new P2pEth2Depositor contract
    */
    event P2pEth2DepositorSet(address indexed _p2pEth2Depositor);

    // Functions

    /**
    * @notice Set a new reference implementation of FeeDistributor contract
    * @param _referenceFeeDistributor the address of the new reference implementation contract
    */
    function setReferenceInstance(address _referenceFeeDistributor) external;

    /**
    * @notice Creates a FeeDistributor instance for a client
    * @dev Emits `FeeDistributorCreated` event with the address of the newly created instance
    * @dev _referrerConfig can be zero if there is no referrer.
    * @param _clientConfig address and basis points (percent * 100) of the client
    * @param _referrerConfig address and basis points (percent * 100) of the referrer.
    * @param _validatorData clientOnlyClRewards, firstValidatorId, and validatorCount
    * @return newFeeDistributorAddress user FeeDistributor instance that has just been deployed
    */
    function createFeeDistributor(
        IFeeDistributor.FeeRecipient calldata _clientConfig,
        IFeeDistributor.FeeRecipient calldata _referrerConfig,
        IFeeDistributor.ValidatorData calldata _validatorData
    ) external returns (address newFeeDistributorAddress);

    /**
     * @dev Returns the reference FeeDistributor contract address
     */
    function getReferenceFeeDistributor() external view returns (address);
}

// File: contracts/p2pEth2Depositor/IP2pEth2Depositor.sol



// SPDX-FileCopyrightText: 2023 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "../@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../feeDistributor/IFeeDistributor.sol";

/**
 * @dev External interface of P2pEth2Depositor declared to support ERC165 detection.
 */
interface IP2pEth2Depositor is IERC165 {
    // Events

    /**
    * @notice Emits when a new batch deposit has been made successfully
    * @param _from the address of the depositor
    * @param _newFeeDistributorAddress user FeeDistributor instance that has just been deployed
    * @param _firstValidatorId validator Id (number of all deposits previously made to ETH2 DepositContract plus 1)
    * @param _validatorCount number of ETH2 deposits made with 1 P2pEth2Depositor's deposit
    */
    event P2pEth2DepositEvent(
        address indexed _from,
        address indexed _newFeeDistributorAddress,
        uint64 indexed _firstValidatorId,
        uint256 _validatorCount
    );

    /**
     * @dev Function that allows to deposit up to 1000 nodes at once.
     *
     * - pubkeys                - Array of BLS12-381 public keys.
     * - withdrawal_credentials - Array of commitments to a public keys for withdrawals.
     * - signatures             - Array of BLS12-381 signatures.
     * - deposit_data_roots     - Array of the SHA-256 hashes of the SSZ-encoded DepositData objects.
     */
    function deposit(
        bytes[] calldata pubkeys,
        bytes calldata withdrawal_credentials, // 1, same for all
        bytes[] calldata signatures,
        bytes32[] calldata deposit_data_roots,
        IFeeDistributor.FeeRecipient calldata _clientConfig,
        IFeeDistributor.FeeRecipient calldata _referrerConfig
    ) external payable;
}

// File: contracts/@openzeppelin/contracts/utils/introspection/IERC165.sol



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

pragma solidity 0.8.10;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: contracts/access/IOwnable.sol



// SPDX-FileCopyrightText: 2023 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

/**
 * @dev External interface of Ownable.
 */
interface IOwnable {
    /**
     * @dev Returns the address of the current owner.
     */
    function owner() external view returns (address);
}

// File: contracts/feeDistributor/IFeeDistributor.sol



// SPDX-FileCopyrightText: 2023 P2P Validator <info@p2p.org>
// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import "../@openzeppelin/contracts/utils/introspection/IERC165.sol";

/**
 * @dev External interface of FeeDistributor declared to support ERC165 detection.
 */
interface IFeeDistributor is IERC165 {

    /**
     * @dev 256bits-wide structure to store recipient address along with its basisPoints
     */
    struct FeeRecipient {
        /**
        * @notice basis points (percent * 100) of EL rewards that should go to the recipient
        */
        uint96 basisPoints;

        /**
        * @notice address of the recipient
        */
        address payable recipient;
    }

    /**
    * @dev 256bits-wide structure to store clientOnlyClRewards, firstValidatorId, and validatorCount
     */
    struct ValidatorData {
        /**
        * @notice amount of CL rewards (in Wei) that should belong to the client only
        * and should not be considered for splitting between the service and the referrer
        */
        uint176 clientOnlyClRewards;

        /**
        * @notice Validator Id (number of all deposits previously made to ETH2 DepositContract plus 1)
        */
        uint64 firstValidatorId;

        /**
        * @notice Number of validators corresponding to a given FeeDistributor instance, equal to the number of ETH2 deposits made with 1 P2pEth2Depositor's deposit
        */
        uint16 validatorCount;
    }

    // Events

    /**
    * @notice Emits on successful withdrawal
    * @param _serviceAmount how much wei service received
    * @param _clientAmount how much wei client received
    * @param _referrerAmount how much wei referrer received
    */
    event Withdrawn(uint256 _serviceAmount, uint256 _clientAmount, uint256 _referrerAmount);

    /**
    * @notice Emits once the client and the optional referrer have been set.
    * @param _client address of the client.
    * @param _clientBasisPoints basis points (percent * 100) of EL rewards that should go to the client
    * @param _referrer address of the referrer.
    * @param _referrerBasisPoints basis points (percent * 100) of EL rewards that should go to the referrer
    */
    event Initialized(
        address indexed _client,
        uint96 _clientBasisPoints,
        address indexed _referrer,
        uint96 _referrerBasisPoints
    );

    /**
    * @notice Emits if case there was some ether left after `withdraw` and it has been sent successfully.
    * @param _to destination address for ether.
    * @param _amount how much wei the destination address received.
    */
    event EtherRecovered(address indexed _to, uint256 _amount);

    /**
    * @notice Emits if case there was some ether left after `withdraw` and it has failed to recover.
    * @param _to destination address for ether.
    * @param _amount how much wei the destination address should have received, but didn't.
    */
    event EtherRecoveryFailed(address indexed _to, uint256 _amount);

    // Functions

    /**
    * @notice Set client address.
    * @dev Could not be in the constructor since it is different for different clients.
    * @dev _referrerConfig can be zero if there is no referrer.
    * @param _clientConfig address and basis points (percent * 100) of the client
    * @param _referrerConfig address and basis points (percent * 100) of the referrer.
    * @param _validatorData clientOnlyClRewards, firstValidatorId, and validatorCount
    */
    function initialize(
        FeeRecipient calldata _clientConfig,
        FeeRecipient calldata _referrerConfig,
        ValidatorData calldata _validatorData
    ) external;

    /**
    * @notice Withdraw the whole balance of the contract according to the pre-defined basis points.
    * @dev In case someone (either service, or client, or referrer) fails to accept ether,
    * the owner will be able to recover some of their share.
    * This scenario is very unlikely. It can only happen if that someone is a contract
    * whose receive function changed its behavior since FeeDistributor's initialization.
    * It can never happen unless the receiving party themselves wants it to happen.
    * We strongly recommend against intentional reverts in the receive function
    * because the remaining parties might call `withdraw` again multiple times without waiting
    * for the owner to recover ether for the reverting party.
    * In fact, as a punishment for the reverting party, before the recovering,
    * 1 more regular `withdraw` will happen, rewarding the non-reverting parties again.
    * `recoverEther` function is just an emergency backup plan and does not replace `withdraw`.
    *
    * @param _proof Merkle proof (the leaf's sibling, and each non-leaf hash that could not otherwise be calculated without additional leaf nodes)
    * @param _amount total CL rewards earned by all validators (see _validatorCount)
    */
    function withdraw(
        bytes32[] calldata _proof,
        uint256 _amount
    ) external;

    /**
     * @dev Returns the factory address
     */
    function factory() external view returns (address);

    /**
     * @dev Returns the service address
     */
    function service() external view returns (address);

    /**
     * @dev Returns the client address
     */
    function client() external view returns (address);

    /**
     * @dev Returns the client basis points
     */
    function clientBasisPoints() external view returns (uint256);

    /**
    * @dev Returns the referrer address
     */
    function referrer() external view returns (address);

    /**
     * @dev Returns the referrer basis points
     */
    function referrerBasisPoints() external view returns (uint256);

    /**
    * @dev Returns First Validator Id
    */
    function firstValidatorId() external view returns (uint256);

    /**
    * @dev Returns a portion of CL rewards that should not be counted during withdraw (belongs to client only)
    */
    function clientOnlyClRewards() external view returns (uint256);

    /**
    * @dev Returns validator count
    */
    function validatorCount() external view returns (uint256);
}