// File: contracts/MultipleDeposit.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Interfaces/IDepositContract.sol";

contract MultipleDeposit {
    // address of the ETH2 deposit contracts on their respective networks and can be verified here: https://ethereum.org/en/staking/deposit-contract/
    // verified deposit contract: https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa
    address constant public MAINNET_DEPOSIT_ACCOUNT = 0x00000000219ab540356cBB839Cbe05303d7705Fa;
    mapping (bytes32 => uint) public depositAmount; // amount sent per pubkey
    IDepositContract public depositContract;

    uint constant public STAKE_AMOUNT = 32 ether;   // best amount for 1 validator
    event ValidatorDeposited(address indexed _depositor, bytes _pubkey, uint _amount);

    constructor()
    {
        require(block.chainid == 1, "MultipleDeposit: mainnet only");
        depositContract = IDepositContract(MAINNET_DEPOSIT_ACCOUNT);
    }

    /// @dev make multiple deposits for different pubkey and withdrawal address combination
    /// for detail meaning of the parameters, check the mainnet deposit contract source
    /// these data are generated by the staking deposit cli https://github.com/ethereum/staking-deposit-cli
    /// @param _forkVersion intended network for the given deposit json reserved for testnet use only
    /// @param _pubkey list of pubkeys of the stakers
    /// @param _withdrawal_credentials withdrawal address associated with each pubkey 
    /// @param _signature associated signature(signed by the corresponding pubkey holder(s))
    /// @param _deposit_data_root associated data root to prevent malformed input
    function depositMultipleValidators(
        uint _forkVersion,
        bytes[] calldata _pubkey,
        bytes[] calldata _withdrawal_credentials,
        bytes[] calldata _signature,
        bytes32[] calldata _deposit_data_root) 
    external payable
    {
        uint noValidators = _pubkey.length;

        require(0 == _forkVersion, "MultipleDeposit: deposit to wrong chain");

        // supplied data must match
        require( (noValidators > 0)
                && (_withdrawal_credentials.length == noValidators) 
                && (_signature.length == noValidators)
                && (_deposit_data_root.length == noValidators), "MultipleDeposit: validator params don't match");

        // tx caller must supply correct ETH for the deposit
        require(msg.value == noValidators*STAKE_AMOUNT, "MultipleDeposit: incorrect ETH amount");

        for (uint ii; ii < noValidators;) {
            // prevent sending too much
            bytes32 phash = keccak256(_pubkey[ii]);
            depositAmount[phash] += STAKE_AMOUNT;
            require(depositAmount[phash] <= 32 ether, "MultipleDeposit: already sent 32 ETH");
            
            // withdrawal wallet locked-in the same as deposit wallet for the protection of validator owner
            _checkWithdrawalCredential(_withdrawal_credentials[ii]);

            // make the deposit to the deposit contract that would be picked up and accounted under the given pubkey in the Consensus Layer
            // the data would be checked by the deposit contract for correctness and would revert if it is wrong
            depositContract.deposit{value: STAKE_AMOUNT}(_pubkey[ii], _withdrawal_credentials[ii], _signature[ii], _deposit_data_root[ii]);

            // event for tracking purpose of who and when the deposit is made
            emit ValidatorDeposited(msg.sender, _pubkey[ii], STAKE_AMOUNT);
            
            // gas optimization
            unchecked {
                ii++;
            }
        }
    }

    // ensure ETH provider is the same as withdrawal address, this means there is no 'deposit onbehalf of'
    function _checkWithdrawalCredential(bytes calldata _withdrawal_credential) private view {
            bytes memory xx = _withdrawal_credential;
            bytes1 prefix = xx[0];
            xx[0] = 0;
            (address eth1Address) = abi.decode(xx, (address));
            require(prefix == bytes1(uint8(1)) && msg.sender == eth1Address, "MultipleDeposit: ETH1 address not match");
    }
}

// File: contracts/Interfaces/IDepositContract.sol



//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

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);
}