// File: @openzeppelin/contracts/access/Ownable.sol



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

pragma solidity ^0.8.0;

import "../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.
 *
 * By default, the owner account will be the one that deploys the contract. 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;

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

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

    /**
     * @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 {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _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: @openzeppelin/contracts/utils/Context.sol



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

pragma solidity ^0.8.0;

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

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

// File: contracts/contracts-BatchDepositWithELRVault/BatchDepositWithELRVault.sol



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

import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IDepositContract.sol";
import "./interfaces/IBatchDepositWithELRVault.sol";

contract BatchDepositWithELRVault is IBatchDepositWithELRVault, Ownable {
    address public immutable depositContract;

    /// @notice Constant value
    uint256 public constant DEPOSIT_AMOUNT = 32 ether;
    
    /// @notice Maximum number of validators activated for a single transaction
    uint256 public maxPerDeposit;

    /// @notice Determine if the pubkey is used
    mapping(bytes32 => bool) internal existingPubKeys;

    constructor(address _depositContract, uint256 _maxPerDeposit) payable {
        depositContract = _depositContract;
        maxPerDeposit = _maxPerDeposit;
    }

    /// @dev update maxPerDeposit
    /// @param newMaxPerDeposit new maxPerDeposit value
    function updateMaxPerDeposit(uint256 newMaxPerDeposit) external onlyOwner {
        if(newMaxPerDeposit == 0) revert ZeroValueSet();
        if(newMaxPerDeposit == maxPerDeposit) revert RepeatSetup();
        maxPerDeposit = newMaxPerDeposit; 
        emit UpdateMaxPerDeposit(newMaxPerDeposit);
    }

    /// @dev batch deposit ETH to ETH2 depositContract
    /// @param pubkeys array of publickey
    /// @param withdrawalCredentials array of withdrawal_credential
    /// @param signatures array of signature
    /// @param depositDataRoots array of depositDataRoot
    /// @param tag user`s tag
    function batchDeposit(
        uint256 quantity,
        bytes[] calldata pubkeys, 
        bytes[] calldata withdrawalCredentials, 
        bytes[] calldata signatures,
        bytes32[] calldata depositDataRoots,
        bytes32 tag
        ) public payable {
        if(quantity > maxPerDeposit) revert ExceedingMaxLimit();
        if(msg.value != quantity * DEPOSIT_AMOUNT) revert InvalidETHAmount();
        if(pubkeys.length != quantity) revert PubkeysCountError(); 
        if(withdrawalCredentials.length != quantity) revert WithdrawalCredentialsCountError();
        if(signatures.length != quantity) revert SignaturesCountError(); 
        if(depositDataRoots.length != quantity) revert DepositDataRootsCountError();

        for(uint256 i = 0; i < quantity; ) {
            if(existingPubKeys[keccak256(pubkeys[i])]) revert PubkeyUsed();
            existingPubKeys[keccak256(pubkeys[i])] = true;
            IDepositContract(depositContract).deposit{value: DEPOSIT_AMOUNT}(pubkeys[i], abi.encodePacked(withdrawalCredentials[i]), signatures[i], depositDataRoots[i]);

            unchecked { ++i; }
        }

        emit BatchDeposited(msg.sender, tag, pubkeys, msg.value);
    }

    /// @dev sweep ETH in this contract 
    /// @param receiver receiver address
    function sweep(address receiver) public onlyOwner {
        if(address(receiver) == address(0)) revert ZeroValueSet();
        uint256 currentBalance = address(this).balance;
        if(currentBalance == 0) revert InvalidETHAmount();
        (bool sent, ) = payable(receiver).call{value: currentBalance}("");
        require(sent, "failed");

        emit Swept(msg.sender, receiver, currentBalance);
    }

    receive() external payable {}
}

// File: contracts/contracts-BatchDepositWithELRVault/interfaces/IBatchDepositWithELRVault.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
 
interface IBatchDepositWithELRVault {

    /// @notice Thrown when zero value is set 
    error ZeroValueSet();

    /// @notice Thrown when values are set repeatedly
    error RepeatSetup();
    
    /// @notice Thrown when ETH amount error which is deposited
    error InvalidETHAmount();

    /// @notice The number of validators activated by a user at a time must not exceed the maxPerDeposit
    error ExceedingMaxLimit();

    /// @notice Thrown when Pubkeys' amount error
    error PubkeysCountError();

    /// @notice Thrown when WithdrawalCredentials' amount error
    error WithdrawalCredentialsCountError();

    /// @notice Thrown when Signatures' amount error
    error SignaturesCountError();

    /// @notice Thrown when DepositDataRoots' amount error
    error DepositDataRootsCountError();

    /// @notice Thrown when single pubkey is used twice
    error PubkeyUsed();

    /// @notice Thrown when single pubkey length error
    error PubkeyLengthError();

    /// @notice Thrown when single withdrawalCredential length error
    error WithdrawalCredentialLengthError();

    /// @notice Thrown when single signature length error
    error SignatureLengthError();

    event BatchDeposited(address indexed addr, bytes32 tag, bytes[] pubKeys, uint256 totalETHDeposited);
    
    event UpdateMaxPerDeposit(uint256 newMaxPerDeposit);

    event Swept(address indexed operator, address indexed receiver, uint256 ETHAmount);
}
// File: contracts/contracts-BatchDepositWithELRVault/interfaces/IDepositContract.sol



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

interface IDepositContract {
    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable;
}