---
eip: 7251
title: Increase the MAX_EFFECTIVE_BALANCE
description: Allow validators to have larger effective balances, while maintaining the 32 ETH lower bound.
author: mike (@michaelneuder), Francesco (@fradamt), dapplion (@dapplion), Mikhail (@mkalinin), Aditya (@adiasg), Justin (@justindrake), lightclient (@lightclient), Felix Lange (@fjl)
discussions-to: https://ethereum-magicians.org/t/eip-7251-increase-the-max-effective-balance/15982
status: Final
type: Standards Track
category: Core
created: 2023-06-28
requires: 7002, 7685
---
## Abstract

Increases the constant `MAX_EFFECTIVE_BALANCE`, while keeping the minimum staking balance `32 ETH`. This permits large node operators to consolidate into fewer validators while also allowing solo-stakers to earn compounding rewards and stake in more flexible increments.

## Motivation

As of October 3, 2023, there are currently over 830,000 validators participating in the consensus layer. The size of this set continues to grow due, in part, to the `MAX_EFFECTIVE_BALANCE`, which limits the stake of a single validator to `32 ETH`. This leads to large amounts of "redundant validators", which are controlled by a single entity, possibly running on the same beacon node, but with distinct BLS signing keys. The limit on the `MAX_EFFECTIVE_BALANCE` is technical debt from the original sharding design, in which subcommittees (not the attesting committee but the committee calculated in `is_aggregator`) needed to be majority honest. As a result, keeping the weights of subcommittee members approximately equal reduced the risk of a single large validator containing too much influence. Under the current design, these subcommittees are only used for attestation aggregation, and thus only have a `1/N` honesty assumption.

With the security model of the protocol no longer dependent on a low value for `MAX_EFFECTIVE_BALANCE`, we propose raising this value while keeping the minimum validator threshold of `32 ETH`. This increase aims to reduce the validator set size, thereby reducing the number of P2P messages over the network, the number of BLS signatures that need to be aggregated each epoch, and the `BeaconState` memory footprint. This change adds value for both small and large validators. Large validators can consolidate to run fewer validators and thus fewer beacon nodes. Small validators now benefit from compounding rewards and the ability to stake in more flexible increments (e.g., the ability to stake `40 ETH` instead of needing to accumulate `64 ETH` to run two validators today).

## Specification

### Constants

#### Execution layer

| Name | Value | Comment |
| - | - | - |
| `CONSOLIDATION_REQUEST_TYPE` | `0x02` | The [EIP-7685](./eip-7685.md) type prefix for consolidation request |
| `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS` | `0x0000BBdDc7CE488642fb579F8B00f3a590007251` | Where to call and store relevant details about consolidation request mechanism |
| `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | Address used to invoke system operation on contract |
| `EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT` | `0` | |
| `CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT` | `1` | |
| `CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT` | `2` | Pointer to the head of the consolidation request message queue |
| `CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT` | `3` | Pointer to the tail of the consolidation request message queue |
| `CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET` | `4` | The start memory slot of the in-state consolidation request message queue |
| `MAX_CONSOLIDATION_REQUESTS_PER_BLOCK` | `2` | Maximum number of consolidation requests that can be dequeued into a block |
| `TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK` | `1` | |
| `MIN_CONSOLIDATION_REQUEST_FEE` | `1` | |
| `CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION` | `17` | |
| `EXCESS_INHIBITOR` | `2**256-1` | Excess value used to compute the fee before the first system call |

#### Consensus layer

| Name | Value |
| - | - |
| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` |
| `MIN_ACTIVATION_BALANCE` | `Gwei(2**5 * 10**9)`  (32 ETH) |
| `MAX_EFFECTIVE_BALANCE_ELECTRA` | `Gwei(2**11 * 10**9)` (2048 ETH) |

### Execution layer

#### Consolidation request

The new consolidation request is an [EIP-7685](./eip-7685.md) request with type `0x02` consisting of the following fields:

1. `source_address`: `Bytes20`
2. `source_pubkey`: `Bytes48`
3. `target_pubkey`: `Bytes48`

The [EIP-7685](./eip-7685.md) encoding of a consolidation request is as follows. Note we simply return the encoded request value as returned by the contract.

```python
request_type = CONSOLIDATION_REQUEST_TYPE
request_data = dequeue_consolidation_requests()
```

#### Consolidation request contract

The contract has three different code paths, which can be summarized at a high level as follows:

1. Add consolidation request - requires a `96` byte input, concatenated public keys of the source and the target validators.
2. Fee getter - if the input length is zero, return the current fee required to add a consolidation request.
3. System process - if called by system address, pop off the consolidation requests for the current block from the queue.

##### Add Consolidation Request

If call data input to the contract is exactly `96` bytes, perform the following:

1. Ensure enough ETH was sent to cover the current consolidation request fee (`msg.value >= get_fee()`)
2. Increase consolidation request count by `1` for the current block (`increment_count()`)
3. Insert a consolidation request into the queue for the source address and pubkeys of the source and the target (`insert_consolidation_request_into_queue()`)

Specifically, the functionality is defined in pseudocode as the function `add_consolidation_request()`:

```python
def add_consolidation_request(Bytes48: source_pubkey, Bytes48: target_pubkey):
    """
    Add consolidation request adds new request to the consolidation request queue, so long as a sufficient fee is provided.
    """

    # Verify sufficient fee was provided.
    fee = get_fee()
    require(msg.value >= fee, 'Insufficient value for fee')

    # Increment consolidation request count.
    count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, count + 1)

    # Insert into queue.
    queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 4
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, source_pubkey[0:32])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, source_pubkey[32:48] ++ target_pubkey[0:16])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3, target_pubkey[16:48])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
```

###### Fee calculation

The following pseudocode can compute the cost of an individual consolidation request, given a certain number of excess consolidation requests.

```python
def get_fee() -> int:
    excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
    require(excess != EXCESS_INHIBITOR, 'Inhibitor still active')
    return fake_exponential(
        MIN_CONSOLIDATION_REQUEST_FEE,
        excess,
        CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION
    )

def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
    i = 1
    output = 0
    numerator_accum = factor * denominator
    while numerator_accum > 0:
        output += numerator_accum
        numerator_accum = (numerator_accum * numerator) // (denominator * i)
        i += 1
    return output // denominator
```

##### Fee Getter

When the input to the contract is length zero, interpret this as a get request for the current fee, i.e. the contract returns the result of `get_fee()`.

##### System Call

At the end of processing any execution block where this EIP is active (i.e. after processing all transactions and after performing the block body consolidation requests validations), call the contract at `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS` as `SYSTEM_ADDRESS` with no calldata. The invocation triggers the following:

* The contract's queue is updated based on consolidation requests dequeued and the consolidation requests queue head/tail are reset if the queue has been cleared (`dequeue_consolidation_requests()`)
* The contract's excess consolidation requests are updated based on usage in the current block (`update_excess_consolidation_requests()`)
* The contract's consolidation requests count is reset to `0` (`reset_consolidation_requests_count()`)

Each consolidation request must appear in the EIP-7685 requests list in the exact order returned by `dequeue_consolidation_requests()`.

Additionally, the system call and the processing of that block must conform to the following:

* The call has a dedicated gas limit of `30_000_000`.
* Gas consumed by this call does not count against the block’s overall gas usage.
* Both the gas limit assigned to the call and the gas consumed are excluded from any checks against the block’s gas limit.
* The call does not follow [EIP-1559](./eip-1559.md) fee burn semantics — no value should be transferred as part of this call.
* If there is no code at `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS`, the corresponding block **MUST** be marked invalid.
* If the call to the contract fails or returns an error, the block **MUST** be invalidated.

The functionality triggered by the system call is defined in pseudocode as the function `process_consolidation_requests()`:

```python
###################
# Public function #
###################

def process_consolidation_requests():
    reqs = dequeue_consolidation_requests()
    update_excess_consolidation_requests()
    reset_consolidation_requests_count()
    return ssz.serialize(reqs)

###########
# Helpers #
###########

class ConsolidationRequest(object):
    source_address: Bytes20
    source_pubkey: Bytes48
    target_pubkey: Bytes48

def dequeue_consolidation_requests():
    queue_head_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT)
    queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    num_in_queue = queue_tail_index - queue_head_index
    num_dequeued = min(num_in_queue, MAX_CONSOLIDATION_REQUESTS_PER_BLOCK)

    reqs = []
    for i in range(num_dequeued):
        queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 4
        source_address = address(sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot)[0:20])
        source_pubkey = (
            sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1)[0:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[0:16]
        )
        target_pubkey = (
            sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[16:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3)[0:32]
        )
        req = ConsolidationRequest(
            source_address=Bytes20(source_address),
            source_pubkey=Bytes48(source_pubkey),
            target_pubkey=Bytes48(target_pubkey)
        )
        reqs.append(req)

    new_queue_head_index = queue_head_index + num_dequeued
    if new_queue_head_index == queue_tail_index:
        # Queue is empty, reset queue pointers
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0)
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0)
    else:
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index)

    return reqs

def update_excess_consolidation_requests():
    previous_excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
    # Check if excess needs to be reset to 0 for first iteration after activation
    if previous_excess == EXCESS_INHIBITOR:
        previous_excess = 0

    count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)

    new_excess = 0
    if previous_excess + count > TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK:
        new_excess = previous_excess + count - TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK

    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT, new_excess)

def reset_consolidation_requests_count():
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, 0)
```

##### Bytecode

```asm
caller
push20 0xfffffffffffffffffffffffffffffffffffffffe
eq
push1 0xd3
jumpi

push1 0x11
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
push2 0x019a
jumpi

push1 0x01
dup3
mul
push1 0x01
swap1
push0

jumpdest
push0
dup3
gt
iszero
push1 0x68
jumpi

dup2
add
swap1
dup4
mul
dup5
dup4
mul
swap1
div
swap2
push1 0x01
add
swap2
swap1
push1 0x4d
jump

jumpdest
swap1
swap4
swap1
div
swap3
pop
pop
pop
calldatasize
push1 0x60
eq
push1 0x88
jumpi

calldatasize
push2 0x019a
jumpi

callvalue
push2 0x019a
jumpi

push0
mstore
push1 0x20
push0
return

jumpdest
callvalue
lt
push2 0x019a
jumpi

push1 0x01
sload
push1 0x01
add
push1 0x01
sstore
push1 0x03
sload
dup1
push1 0x04
mul
push1 0x04
add
caller
dup2
sstore
push1 0x01
add
push0
calldataload
dup2
sstore
push1 0x01
add
push1 0x20
calldataload
dup2
sstore
push1 0x01
add
push1 0x40
calldataload
swap1
sstore
caller
push1 0x60
shl
push0
mstore
push1 0x60
push0
push1 0x14
calldatacopy
push1 0x74
push0
log0
push1 0x01
add
push1 0x03
sstore
stop

jumpdest
push1 0x03
sload
push1 0x02
sload
dup1
dup3
sub
dup1
push1 0x02
gt
push1 0xe7
jumpi

pop
push1 0x02

jumpdest
push0

jumpdest
dup2
dup2
eq
push2 0x0129
jumpi

dup3
dup2
add
push1 0x04
mul
push1 0x04
add
dup2
push1 0x74
mul
dup2
sload
push1 0x60
shl
dup2
mstore
push1 0x14
add
dup2
push1 0x01
add
sload
dup2
mstore
push1 0x20
add
dup2
push1 0x02
add
sload
dup2
mstore
push1 0x20
add
swap1
push1 0x03
add
sload
swap1
mstore
push1 0x01
add
push1 0xe9
jump

jumpdest
swap2
add
dup1
swap3
eq
push2 0x013b
jumpi

swap1
push1 0x02
sstore
push2 0x0146
jump

jumpdest
swap1
pop
push0
push1 0x02
sstore
push0
push1 0x03
sstore

jumpdest
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
iszero
push2 0x0173
jumpi

pop
push0

jumpdest
push1 0x01
sload
push1 0x01
dup3
dup3
add
gt
push2 0x0188
jumpi

pop
pop
push0
push2 0x018e
jump

jumpdest
add
push1 0x01
swap1
sub

jumpdest
push0
sstore
push0
push1 0x01
sstore
push1 0x74
mul
push0
return

jumpdest
push0
push0
revert
```

##### Deployment

The consolidation requests contract is deployed like any other smart contract. A special synthetic address is generated by working backwards from the desired deployment transaction:

```json
{
  "type": "0x0",
  "nonce": "0x0",
  "to": null,
  "gas": "0x3d090",
  "gasPrice": "0xe8d4a51000",
  "maxPriorityFeePerGas": null,
  "maxFeePerGas": null,
  "value": "0x0",
  "input": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f5561019e80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd",
  "v": "0x1b",
  "r": "0x539",
  "s": "0xc0730f92dc275b663d377a7cbb141b6600052",
  "hash": "0x379269d571beff3ed1d8eba3abb24076a7267b0eaf0cc66d728fb0544f5a690d"
}
```

```
Sender: 0x13d1913d623E6a9D8811736359E50fD31Fe54fCA
Address: 0x0000BBdDc7CE488642fb579F8B00f3a590007251
```

### Consensus layer

The defining features of this EIP are:

1. ***Increasing the `MAX_EFFECTIVE_BALANCE`, while creating a `MIN_ACTIVATION_BALANCE`.*** The core feature of allowing variable size validators.
2. ***Allowing for multiple validator indices to be combined through the protocol.*** A mechanism by which large node operators can combine validators without cycling through the exit and activation queues.
3. ***Adding execution layer partial withdrawals (part of [EIP-7002](./eip-7002.md)).*** Allowing Execution Layer messages to trigger partial withdrawals in addition to full exits (e.g., a `100 ETH` validator can remove up to `68 ETH` without exiting the validator).
4. ***Making the initial slashing penalty negligible.*** This reduces the risk of consolidation for large validators.

The [Rationale](#rationale) section contains an explanation for each of these proposed core features. A sketch of the resulting changes to the consensus layer is included below.

1. Add `COMPOUNDING_WITHDRAWAL_PREFIX` and `MIN_ACTIVATION_BALANCE` constants, while introducing the updated value of `MAX_EFFECTIVE_BALANCE` (`MAX_EFFECTIVE_BALANCE_ELECTRA`).
2. Create the `PendingDeposit` container, which is used to track incoming deposits in the weight-based rate limiting mechanism.
3. Update the `BeaconState` with deposit and partial withdrawal queues, fields needed for deposit, and exit queue weight-based rate limiting.
4. Modify `is_eligible_for_activation_queue` to check against `MIN_ACTIVATION_BALANCE` rather than `MAX_EFFECTIVE_BALANCE`.
5. Modify `get_validator_churn_limit` to depend on the validator weight rather than the validator count.
6. Create a helper `compute_exit_epoch_and_update_churn` to calculate the exit epoch based on the current pending withdrawals.
6. Modify `initiate_validator_exit` to rate limit the exit queue by balance rather than the number of validators.
7. Modify `initialize_beacon_state_from_eth1` to use `MIN_ACTIVATION_BALANCE`.
9. Modify `process_registry_updates` to activate all eligible validators.
10. Add a per-epoch helper, `process_pending_deposits`, to consume some of the pending deposits.
10. Modify `get_validator_from_deposit` to initialize the effective balance to zero (it's updated by the pending deposit flow).
11. Modify `process_deposit` to store incoming deposits in `state.pending_deposits`.
12. Modify `compute_weak_subjectivity_period` to use the new churn limit function.
13. Add `has_compounding_withdrawal_credential` to check for the `0x02` credential.
14. Modify `is_fully_withdrawable_validator` to check for compounding credentials.
15. Add `get_validator_excess_balance` to calculate the excess balance of validators.
16. Modify `is_partially_withdrawable_validator` to check for excess balance.
17. Modify `get_expected_withdrawals` to use excess balance and process pending partial withdrawals.
18. Add `process_consolidation` to initiate in-protocol validator consolidation and switch a validator to compounding withdrawal credentials.
19. Limits the usage of exit churn to 256 ETH (equivalent to 8 validators with 32 ETH effective balance), and defers the surplus of the churn to process consolidations.

Full consensus layer specification can be found in [`./electra/beacon-chain.md`](https://github.com/ethereum/consensus-specs/blob/834e40604ae4411e565bd6540da50b008b2496dc/specs/electra/beacon-chain.md)

## Rationale

This EIP aims to reduce the total number of validators without changing anything about the economic security of the protocol. It provides a mechanism by which large node operators who control significant amounts of stake can consolidate into fewer validators. We analyze the reasoning behind each of the core features.

### Increasing the `MAX_EFFECTIVE_BALANCE`, while creating a `MIN_ACTIVATION_BALANCE`

While increasing the `MAX_EFFECTIVE_BALANCE` to allow larger-stake validators, it is important to keep the lower bound of `32 ETH` (by introducing a new constant – `MIN_ACTIVATION_BALANCE`) to encourage solo-staking.

### Allowing for multiple validator indices to be combined through the protocol

For large staking pools that already control thousands of validators, exiting and re-entering would be extremely slow and costly. The adoption of the EIP will be much higher by allowing in-protocol consolidation.

### Adding execution layer partial withdrawals (part of [EIP-7002](./eip-7002.md))

For validators that choose to raise their effective balance ceiling, allowing for custom partial withdrawals triggered from the execution layer increases the flexibility of the staking configurations. Validators can choose when and how much they withdraw but will have to pay gas for the EL transaction.

### Making the initial slashing penalty negligible

To encourage consolidation, we could modify the slashing penalties. The biggest hit comes from the initial penalty of `1/32` of the validator's effective balance. Since this scales linearly on the effective balance, the higher-stake validators directly incur higher risk. By changing the scaling properties, we could make consolidation more attractive.

### Consolidation contract parameter values

The consolidation smart contract uses a fee mechanism to rate limit the number of requests per block. Details of the fee mechanism are available in [EIP-7002](./eip-7002.md#fee-update-rule).

`TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK` is chosen to be `1` to rate limit consolidation requests as much as possible.
`MAX_CONSOLIDATION_REQUESTS_PER_BLOCK` is chosen to be `2` to allow for switching a validator to compounding credentials and requesting a consolidation in the same block.

One consolidation per block is still higher than the size of consolidation churn which can lead to unbounded growth of the consolidation queue.
Thus, there is a hard limit on the consolidation queue size equal to 262,144 requests which is `4 MB` of data in the beacon state.

## Backwards Compatibility

This EIP introduces backward incompatible changes to the block validation rule set and must be accompanied by a hard fork. These changes do not break anything related to current user activity and experience.

## Security Considerations

This change modifies committees and churn, but doesn't significantly impact the security properties.

### Security of attestation committees

Given full consolidation as the worst case, the probability of an adversarial takeover of a committee remains low. Even in a high consolidation scenario, the required share of honest validators remains well below the 2/3 supermajority needed for finality.

### Aggregator selection

In the original sharding roadmap, subcommittees were required to be secure with extremely high probability. Now with the sole responsibility of attestation aggregation, we only require each committee to have at least one honest aggregator. Currently, aggregators are selected through a VRF lottery, targeting several validator units that can be biased by non-consolidated attackers. This proposal changes the VRF lottery to consider weight, so the probability of having at least one honest aggregator is not worse.

### Proposer selection probability

Proposer selection is already weighted by the ratio of their effective balance to `MAX_EFFECTIVE_BALANCE`. Due to the lower probabilities, this change will slightly increase the time it takes to calculate the next proposer index.

### Sync committee selection probability

Sync committee selection is also already weighted by effective balance, so this proposal does not require modifications to the sync protocol. Light clients can still check that a super-majority of participants have signed an update irrespective of their weights since we maintain a weight-based selection probability.

### Churn invariants

This proposal maintains the activation and exit churn invariants limiting active weight instead of validator count. Balance top-ups are now handled explicitly, being subject to the same activation queue as full deposits.

### Fee Overpayment

Calls to the system contract require a fee payment defined by the current contract state. Overpaid fees are not returned to the caller. It is not generally possible to compute the exact required fee amount ahead of time. When adding a consolidation request from a contract, the contract can perform a read operation to check for the current fee and then pay exactly the required amount. Here is an example in Solidity:

```
function addConsolidation(bytes memory srcPubkey, bytes memory targetPubkey, uint64 requestFeeLimit) private {
    assert(srcPubkey.length == 48);
    assert(targetPubkey.length == 48);

    // Read current fee from the contract.
    (bool readOK, bytes memory feeData) = ConsolidationsContract.staticcall('');
    if (!readOK) {
        revert('reading fee failed');
    }
    uint256 fee = uint256(bytes32(feeData));

    // Check the fee is not too high.
    if (fee > requestFeeLimit) {
        revert('fee is too high');
    }

    // Add the request.
    bytes memory callData = bytes.concat(srcPubkey, targetPubkey);
    (bool writeOK,) = ConsolidationsContract.call{value: fee}(callData);
    if (!writeOK) {
        revert('adding request failed');
    }
}
```

Note: the system contract uses the EVM `CALLER` operation (Solidity: `msg.sender`) to get the address used in the consolidation request, i.e. the address that calls the system contract must match the 0x01 withdrawal credential recorded in the beacon state.

Note: the above code reverts if the fee is too high, the fee can change significantly between the creation of a consolidation request transaction and its inclusion into a block, thus, this check is very important to avoid overpayments.

Using an EOA to request consolidations will always result in overpayment of fees. There is no way for an EOA to use a wrapper contract to request a consolidation. And even if a way existed, the gas cost of returning the overage would likely be higher than the overage itself. If requesting consolidations from an EOA to the system contract is desired, we recommend that users perform transaction simulations to estimate a reasonable fee amount to send.

### Consolidation queue hard limit

Consolidations exceeding the hard limit of the consolidation queue (262,144 requests) will be discarded by the consensus layer and will need to be re-submitted,
note that the fee is not refunded in this case.


### System Call failure

Although the likelihood of a failed system call to `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS` is extremely low, the behavior in such cases is well-defined: the block is marked as invalid. This consideration directly follows from [EIP-7002](./eip-7002.md#system-call-failure) 

### Empty Code failure

This EIP should not have been activated if there is no code present at `CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS` (i.e., if the chain is not "ready"). This is also similar to the consideration in [EIP-7002](./eip-7002.md#empty-code-failure) 

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
