Experiment Notebook: Validator Revenue and Profit Yields#

Table of Contents#

Experiment Summary#

The purpose of this notebook is to explore the returns validators can expect from staking in the Ethereum protocol across different time horizons, adoption scenarios, ETH price scenarios, and validator environments.

Experiment Assumptions#

In this experiment notebook, time-domain analyses simulate the transition from the current network network upgrade stage at today’s date onwards (i.e. the transition from the Beacon Chain Stage, to the EIP-1559 Stage, to the Proof-of-Stake Stage), whereas phase-space analyses simulate the current network upgrade stage providing a “snapshot” of the system state at this time.

See assumptions document for further details.

Experiment Setup#

We begin with several experiment-notebook-level preparatory setup operations:

  • Import relevant dependencies

  • Import relevant experiment templates

  • Create copies of experiments

  • Configure and customize experiments

Analysis-specific setup operations are handled in their respective notebook sections.

import setup

import copy
import logging
import numpy as np
import pandas as pd

from experiments.notebooks import visualizations
from experiments.run import run
from experiments.utils import display_code
from model.types import Stage
from model.system_parameters import validator_environments
from model.state_variables import eth_staked, eth_supply
# Enable/disable logging
logger = logging.getLogger()
logger.disabled = True
# Import experiment templates
import experiments.templates.time_domain_analysis as time_domain_analysis
import experiments.templates.cumulative_yield_analysis as cumulative_yield_analysis
import experiments.templates.eth_staked_sweep_analysis as eth_staked_sweep_analysis
import experiments.templates.eth_price_sweep_analysis as eth_price_sweep_analysis
import experiments.templates.eth_price_eth_staked_grid_analysis as eth_price_eth_staked_grid_analysis
# Create a new copy of the relevant simulation for each analysis
simulation_1a = copy.deepcopy(time_domain_analysis.experiment.simulations[0])
simulation_1b = copy.deepcopy(cumulative_yield_analysis.experiment.simulations[0])
simulation_2 = copy.deepcopy(eth_staked_sweep_analysis.experiment.simulations[0])
simulation_3 = copy.deepcopy(eth_price_sweep_analysis.experiment.simulations[0])
simulation_4 = copy.deepcopy(eth_price_eth_staked_grid_analysis.experiment.simulations[0])
simulation_5 = copy.deepcopy(time_domain_analysis.experiment.simulations[0])

Analysis 1: Revenue and Profit Yields Over Time#

This analysis allows the exploration of revenue and profit yields over time, and across three linear adoption scenarios (historical adoption has been approximately linear):

  • Normal adoption: assumes an average of 3 new validators per epoch. These rates correspond to the historical average newly activated validators per epoch between 15 January 2021 and 15 July 2021 as per Beaconscan.

  • Low adoption: assumes an average of 1.5 new validators per epoch, i.e. a 50% lower rate compared to the base scenario

  • High adoption: assumes an average of 4.5 new validators per epoch, i.e. a 50% higher rate compared to the base scenario

Adoption scenarios can be customized by updating the validator_process System Parameter if desired.

The first chart (“Validator Adoption Scenarios”) depicts the three adoption scenarios (i.e. implied ETH staked over time) underlying Analysis 1. Please note that the High Adoption Scenario has non-linear dynamics due to the validator-activation queue-rate limiting. To create custom adoption scenarios, add another entry to the validator_process System Parameter, with either a static per-epoch validator adoption rate, or generate a time-series using the current timestep to index the data.

The second chart (“Revenue and Profit Yields Over Time – At a Glance”) depicts both revenue and profit yields over time and across the three adoption scenarios (i.e. implied ETH staked over time). The ETH price (relevant for profit yields) is by default set to the mean ETH price over the last 12 months. The higher the adoption, the lower both revenue and profit yields. The higher the ETH price, the higher the profit yields; while validator operational costs are fixed in dollars, returns are in ETH and their equivalent dollar value depends on the current ETH price.

The third chart (“Revenue or Profit Yields Over Time”) depicts revenue yields or profit yields (choose using button selector) over the chosen time frame and across the three adoption scenarios (i.e. implied ETH staked over time), and ETH price range. In simple terms, this chart depicts how validators can expect the yield dynamics to change over different adoption and price scenarios. The higher the adoption, the lower both revenue and profit yields. The higher the price, the higher the profit yields.

The fourth chart (“Cumulative Revenue or Profit Yields Over Time”) depicts the cumulative revenue yields or profit yields (choose via button selector) over the chosen time frame, and across the three adoption scenarios (i.e. implied ETH staked over time) and ETH price range. In simple terms, this chart depicts the effective yields a validator can expect over the respective time horizons if they start validating today. The higher the adoption, the lower both revenue and profit yields. The higher the price, the higher the profit yields.

The fifth chart (“Cumulative Revenue or Profit Yields: PoS Issuance, EIP-1559 Priority Fees, MEV”) depicts the cumulative revenue yields over the chosen time frame, and split into its three components PoS Issuance, EIP-1559 Priority Fees, and MEV. This analysis has been made available via the model’s web front-end at www.ethmodel.io.

normal_adoption = simulation_1a.model.params['validator_process'][0](_run=None, _timestep=None)
simulation_1a.model.params.update({
    'validator_process': [
        lambda _run, _timestep: normal_adoption,  # Normal adoption: current 6-month average active validators per epoch from The Graph Subgraph
        lambda _run, _timestep: normal_adoption * 0.5,  # Low adoption: 50%-lower scenario
        lambda _run, _timestep: normal_adoption * 1.5,  # High adoption: 50%-higher scenario
    ],  # New validators per epoch
})
df_1a, _exceptions = run(simulation_1a)
visualizations.plot_number_of_validators_per_subset(df_1a, scenario_names={0: "Normal Adoption", 1: "Low Adoption", 2: "High Adoption"})

The charts below depict revenue and profit yields over time and across the three adoption scenarios shown above (i.e. implied ETH staked over time). The higher the adoption, the lower both revenue and profit yields.

visualizations.plot_yields_per_subset_subplots(
    df_1a,
    subplot_titles=['Normal Adoption', 'Low Adoption', 'High Adoption']
)
visualizations.plot_yields_per_subset(df_1a, scenario_names={0: "Normal Adoption", 1: "Low Adoption", 2: "High Adoption"})
visualizations.plot_cumulative_yields_per_subset(df_1a, simulation_1a.model.params["dt"][0], scenario_names={0: "Normal Adoption", 1: "Low Adoption", 2: "High Adoption"})
simulation_1b.model.params.update({
    'base_fee_process': [
        lambda _run, _timestep: 0,
        lambda _run, _timestep: 30,
    ],
    'priority_fee_process': [
        lambda _run, _timestep: 0,
        lambda _run, _timestep: 2,
    ],
    'mev_per_block': [
        0,
        0,
        0.02,  # ETH - median per-block MEV from https://explore.flashbots.net/
    ]
})
df_1b, _exceptions = run(simulation_1b)
# Calculate stacked revenue yields - subtract one yield value from the next, starting with largest yield scenario
df = df_1b.copy()
subsets = list(reversed(df.subset.unique()))
for subset in subsets:
    df.loc[df.subset == 0, 'cumulative_revenue_yields_stacked'] = df[df.subset == 0]['cumulative_revenue_yields_pct']
    if subset == 0:
        pass
    else:
        df.loc[df.subset == subset, 'cumulative_revenue_yields_stacked'] = \
            df[df.subset == subset]['cumulative_revenue_yields_pct'] - df[df.subset == subset - 1]['cumulative_revenue_yields_pct'].values
fig = visualizations.plot_stacked_cumulative_column_per_subset(
    df, column='cumulative_revenue_yields_stacked',
    scenario_names={0: 'PoS Issuance', 1: 'EIP-1559 Priority Fees', 2: 'MEV'}
)

fig.update_layout(
    title={
        "text":"""
            Cumulative Revenue Yields: PoS Issuance, EIP-1559 Priority Fees, MEV
        """,
    },
    yaxis_title="Cumulative Revenue Yields (%)",
)

fig.show()

Analysis 2: Revenue and Profit Yields Over ETH Staked#

This analysis allows the exploration of revenue and profit yields over a wide range of ETH staked values. Compared to Analysis 1 (which assumed ETH staked ranges as a result of the adoption scenarios), Analysis 2 explicitly shows the yields validators can expect at certain points in the validator-adoption curve. Profit yields are sensitive to ETH price in USD/ETH and plotted at two discrete points – 100 USD/ETH and maximum ETH price over the last 12 months.

df_2, _exceptions = run(simulation_2)
visualizations.plot_revenue_profit_yields_over_eth_staked(df_2)

Analysis 3: Revenue and Profit Yields Over ETH Price#

This analysis allows the exploration of revenue and profit yields over a large range of ETH price values in USD/ETH. The ETH staked is fixed at the currrent ETH staked value updated from Etherscan. Revenue yields are not sensitive to ETH price, hence the horizontal line. Profit yields drop quickly at very low ETH prices and stabilize at higher ETH prices. Validator operational costs are fixed in USD, whereas revenue is in ETH. This causes a “cliff” in the realized profit (revenue - costs) at low ETH prices.

simulation_3.model.params.update({
    'eth_staked_process': [
        # Current ETH staked value updated from Etherscan
        lambda _run, _timestep: eth_staked,
    ]
})
df_3, _exceptions = run(simulation_3)
visualizations.plot_revenue_profit_yields_over_eth_price(df_3)

Analysis 4: Profit Yields Over ETH Staked vs. ETH Price#

This contour chart was created to enable at-a-glance intuition about the relationship between profit yields, validator adoption, and ETH price (and because we like colorful charts). Profit yields are highest when the ETH price is high and adoption is low.

df_4, _exceptions = run(simulation_4)
fig = visualizations.plot_validator_environment_yield_contour(df_4)
fig.write_image("../outputs/validator_environment_yield_contour.png")
fig.show()

This surface chart displays the same data as the charts above and is arguably less readable, but since some folks might appreciate the fanciness of a 3D plot, we decided to keep it.

visualizations.plot_validator_environment_yield_surface(df_4)

Analysis 5: Profit Yields By Validator Environment Over Time#

This analysis allows the exploration of revenue and profit yields per validator environment over time. The analysis is based on the “Normal Adoption” scenario described in Analysis 1. This analysis of course depends heavily on the cost assumptions per validator environment. We encourage the user to review the assumptions document in this context.

StaaS validator environments – which do not incur validator operational costs directly but instead pay a percentage of their total revenue as a fee to the relevant service provider – do not have variation in their profit yield with a stochastic (random) ETH price. Pool validator environments tend to receive better returns than StaaS environments, because their operational costs are shared among all the validators in the pool.

df_5, _exceptions = run(simulation_5)
visualizations.plot_profit_yields_by_environment_over_time(df_5)