# Deposit exploit mitigation ## Exploit description Per the Ethereum consensus layer specification, the validator public key is associated with the withdrawal credentials (WC) on the first valid deposit that uses the public key. Subsequent deposits will use the WC from the first deposit even if another WC are specified allows a node operator to associate the validator’s public key with the validator-controlled WC by front-running a deposit transaction sent by a protocol with another deposit transaction specifying the same public key, validator-controlled WC, and 1 ETH amount. The end state is a validator managing 1 ETH of node operators’ funds and 32 ETH of users’ funds, fully controlled and withdrawable by the node operator. Discussion: https://ethresear.ch/t/deposit-contract-exploit/6528 ## Motivation EIP-7251 increases the potential exploit impact from 32 ETH to 2048 ETH per a single deposit. ## Proposed solution To mitigate the vulnerability, it is proposed to handle scenarios where the address in `withdrawal_credentials` of the deposit does not match the address in withdrawal credentials of the existing validator and not perform such a deposit. Instead, ETH can be withdrawn to the new deposit withdrawal address. This solution does not cover the front-run scenario for `0x00` to another `0x00` withdrawal credentials types. This would require knowing the address of the initiator of the deposit transaction. ```python def apply_deposit(state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64, signature: BLSSignature) -> None: validator_pubkeys = [v.pubkey for v in state.validators] if pubkey not in validator_pubkeys: # Verify the deposit signature (proof of possession) which is not checked by the deposit contract if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature): add_validator_to_registry(state, pubkey, withdrawal_credentials, amount) else: index = ValidatorIndex(validator_pubkeys.index(pubkey)) validator = Validator(state.validators[index]) # [New in EIP-7251] if ( is_execution_withdrawal_credential(withdrawal_credentials) and not is_same_withdrawal_address(withdrawal_credentials, validator.withdrawal_credentials) ): # Withdraw funds to deposit withdrawal address if addresses don't match # # Mitigates the deposit contract front-running exploit # https://ethresear.ch/t/deposit-contract-exploit/6528 return_amount = amount - RETURN_DEPOSIT_FEE queue_withdrawal(index, withdrawal_credentials[12:], return_amount) else: # Increase balance by deposit amount state.pending_balance_deposits.append( PendingBalanceDeposit(index=index, amount=amount)) # Check if valid deposit switch to compounding credentials if ( is_compounding_withdrawal_credential(withdrawal_credentials) and has_eth1_withdrawal_credential(validator) # [Modified in EIP-7251] and is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature) ): switch_to_compounding_validator(state, index) ``` It is assumed that such Withdrawals are put into some list in `BeaconState` and later taken into account in the `get_expected_withdrawals` method. ## Attack vectors **DOS prevention**. Some amount (`RETURN_DEPOSIT_FEE`) may be burned to deinsentivize malicious actions with the withdrawal queue.