BatchDeposit

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/* ───────────── Official Deposit interface ───────────── */
interface IDepositContract {
    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable;

    function get_deposit_root() external view returns (bytes32);
    function get_deposit_count() external view returns (bytes memory);
}

/* ───────────── Minimal upgradeable bases ───────────── */
abstract contract Initializable {
    bool private _initialized;
    bool private _initializing;
    modifier initializer() {
        require(!_initialized || _initializing, "already initialized");
        bool top = !_initializing;
        if (top) {_initializing = true; _initialized = true;}
        _;
        if (top) {_initializing = false;}
    }
    uint256[50] private __gapInitializable;
}

abstract contract OwnableUpgradeable is Initializable {
    address private _owner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    function __Ownable_init(address initialOwner) internal initializer {
        require(initialOwner != address(0), "zero owner");
        _owner = initialOwner;
        emit OwnershipTransferred(address(0), initialOwner);
    }
    function owner() public view returns (address) { return _owner; }
    modifier onlyOwner() { require(_owner == msg.sender, "not owner"); _; }
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "zero owner");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
    function renounceOwnership() public view onlyOwner { revert("renounce disabled"); }
    uint256[49] private __gapOwnable;
}

abstract contract PausableUpgradeable is Initializable {
    bool private _paused;
    event Paused(address account);
    event Unpaused(address account);
    function __Pausable_init() internal initializer { _paused = false; }
    function paused() public view returns (bool) { return _paused; }
    modifier whenNotPaused() { require(!_paused, "paused"); _; }
    modifier whenPaused() { require(_paused, "not paused"); _; }
    function _pause() internal whenNotPaused { _paused = true; emit Paused(msg.sender); }
    function _unpause() internal whenPaused { _paused = false; emit Unpaused(msg.sender); }
    uint256[49] private __gapPausable;
}

/* ───────────── BatchDeposit (proxy-friendly, full features) ───────────── */
contract BatchDeposit is Initializable, OwnableUpgradeable, PausableUpgradeable {
    /* constants */
    uint256 public constant PUBKEY_LENGTH       = 48;
    uint256 public constant SIGNATURE_LENGTH    = 96;
    uint256 public constant CREDENTIALS_LENGTH  = 32;
    uint256 public constant DEPOSIT_AMOUNT      = 32 ether;
    uint256 public constant MAX_VALIDATORS_SOFT = 100; // 与旧版一致

    /* storage */
    address public depositContract;

    /* events */
    event Withdrawn(address indexed payee, uint256 weiAmount);
    event DepositContractUpdated(address indexed oldAddr, address indexed newAddr);

    /* init (for proxy) */
    function initialize(address depositContractAddr, address initialOwner) external initializer {
        require(depositContractAddr != address(0), "bad deposit addr");
        depositContract = depositContractAddr;
        __Ownable_init(initialOwner);
        __Pausable_init();
    }

    /* admin */
    function pause() external onlyOwner { _pause(); }
    function unpause() external onlyOwner { _unpause(); }

    /// The underlying official contract address can only be updated when paused, to prevent misoperations
    function setDepositContract(address newAddr) external onlyOwner whenPaused {
        require(newAddr != address(0), "zero addr");
        emit DepositContractUpdated(depositContract, newAddr);
        depositContract = newAddr;
    }

    function withdraw(address payable to) external onlyOwner {
        require(to != address(0), "zero to");
        uint256 amount = address(this).balance;
        emit Withdrawn(to, amount);
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "withdraw failed");
    }

    function batchDeposit(
        bytes calldata pubkeys,                         // count * 48
        bytes calldata withdrawal_credentials,          // 32
        bytes calldata signatures,                      // count * 96
        bytes32[] calldata deposit_data_roots           // count
    ) external payable whenNotPaused {
        uint256 count = deposit_data_roots.length;
        require(count > 0 && count <= MAX_VALIDATORS_SOFT, "bad count");

        require(pubkeys.length    == count * PUBKEY_LENGTH, "bad pubkeys len");
        require(signatures.length == count * SIGNATURE_LENGTH, "bad sigs len");
        require(withdrawal_credentials.length == CREDENTIALS_LENGTH, "bad cred len");

        uint256 expected = DEPOSIT_AMOUNT * count;
        require(msg.value == expected, "bad value");
        require(msg.value % 1_000_000_000 == 0, "not gwei aligned");

        address depositAddr = depositContract;
        for (uint256 i = 0; i < count; ) {
            bytes memory pk  = _slice(pubkeys, i * PUBKEY_LENGTH, PUBKEY_LENGTH);
            bytes memory sig = _slice(signatures, i * SIGNATURE_LENGTH, SIGNATURE_LENGTH);

            IDepositContract(depositAddr).deposit{value: DEPOSIT_AMOUNT}(
                pk,
                withdrawal_credentials,
                sig,
                deposit_data_roots[i]
            );

            unchecked { ++i; }
        }
    }

    /// Optional: custom amount per validator (for future/L2)
    function batchDepositCustom(
        bytes calldata pubkeys,                         // count * 48
        bytes calldata withdrawal_credentials,          // 32
        bytes calldata signatures,                      // count * 96
        bytes32[] calldata deposit_data_roots,          // count
        uint256 amountPerValidator
    ) external payable whenNotPaused {
        uint256 count = deposit_data_roots.length;
        require(count > 0 && count <= MAX_VALIDATORS_SOFT, "bad count");

        require(pubkeys.length    == count * PUBKEY_LENGTH, "bad pubkeys len");
        require(signatures.length == count * SIGNATURE_LENGTH, "bad sigs len");
        require(withdrawal_credentials.length == CREDENTIALS_LENGTH, "bad cred len");
        require(amountPerValidator > 0, "bad per-amount");

        require(msg.value == amountPerValidator * count, "bad value");
        require(msg.value % 1_000_000_000 == 0, "not gwei aligned");

        address depositAddr = depositContract;
        for (uint256 i = 0; i < count; ) {
            bytes memory pk  = _slice(pubkeys, i * PUBKEY_LENGTH, PUBKEY_LENGTH);
            bytes memory sig = _slice(signatures, i * SIGNATURE_LENGTH, SIGNATURE_LENGTH);

            IDepositContract(depositAddr).deposit{value: amountPerValidator}(
                pk,
                withdrawal_credentials,
                sig,
                deposit_data_roots[i]
            );

            unchecked { ++i; }
        }
    }

    /* utils: calldata -> memory slice */
    function _slice(bytes calldata data, uint256 start, uint256 len) private pure returns (bytes memory out) {
        out = new bytes(len);
        assembly {
            let outPtr := add(out, 0x20)
            calldatacopy(outPtr, add(data.offset, start), len)
        }
    }

    /* storage gap (for future upgrades) */
    uint256[48] private __gap;
}

Tags:
Proxy, Upgradeable|addr:0x53acab043acd9bf9a08075fbd28a4c6574cee062|verified:true|block:23623563|tx:0x8094f8b23c30da17361e815fe7425dc5412de7da9b8080ae0f6bfe307926f465|first_check:1761041895

Submitted on: 2025-10-21 12:18:19

Comments

Log in to comment.

No comments yet.