//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);
}
// 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");
}
}
{
"compilationTarget": {
"contracts/MultipleDeposit.sol": "MultipleDeposit"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1500000
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_depositor","type":"address"},{"indexed":false,"internalType":"bytes","name":"_pubkey","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"ValidatorDeposited","type":"event"},{"inputs":[],"name":"MAINNET_DEPOSIT_ACCOUNT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKE_AMOUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"depositAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositContract","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_forkVersion","type":"uint256"},{"internalType":"bytes[]","name":"_pubkey","type":"bytes[]"},{"internalType":"bytes[]","name":"_withdrawal_credentials","type":"bytes[]"},{"internalType":"bytes[]","name":"_signature","type":"bytes[]"},{"internalType":"bytes32[]","name":"_deposit_data_root","type":"bytes32[]"}],"name":"depositMultipleValidators","outputs":[],"stateMutability":"payable","type":"function"}]