Skip to content

Instantly share code, notes, and snippets.

@gagbo
Last active April 21, 2020 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gagbo/990028fd5b02740f59c5076689e11ae5 to your computer and use it in GitHub Desktop.
Save gagbo/990028fd5b02740f59c5076689e11ae5 to your computer and use it in GitHub Desktop.
Compute estimation of reward rates for ATOM
#!/usr/bin/env python3
from typing import NewType
Atom = NewType("Atom", float)
Seconds = NewType("Seconds", float)
# Network parameters that affect the formula
# Target bonded ratio of ATOMs
TARGET_BONDED_RATIO = 0.67
# Community pool commission (Fixed by network)
POOL_COMMISSION = 0.02
# Assumed average time per block (from the blocks_per_year parameter)
ASSUMED_TIME_PER_BLOCK = Seconds(6.5)
# Inflation rate change (yearly)
INFLATION_RATE_CHANGE = 0.13
# Maximum yearly inflation
INFLATION_MAX_VALUE = 0.20
# Minimum yearly inflation
INFLATION_MIN_VALUE = 0.07
def compute_avg_yearly_inflation(
initial_yearly_inflation: float, bonded_ratio: float
) -> float:
"""
Return the average yearly inflation if we start from initial_yearly_inflation and keep
a constant bonded_ratio.
This add the effect of capping the inflation to the maximum value or the minimum value
Some constants defined in the file help implementing the network rules regarding inflation evolution.
"""
if (
initial_yearly_inflation < INFLATION_MIN_VALUE
or initial_yearly_inflation > INFLATION_MAX_VALUE
):
raise ValueError(
f"Initial inflation ({initial_yearly_inflation}) is not between {INFLATION_MIN_VALUE} and {INFLATION_MAX_VALUE}."
)
inflation_slope = (1 - bonded_ratio / TARGET_BONDED_RATIO) * INFLATION_RATE_CHANGE
unrestricted_end_of_year_inflation = initial_yearly_inflation * (
1 + inflation_slope
)
if (
unrestricted_end_of_year_inflation <= INFLATION_MAX_VALUE
and unrestricted_end_of_year_inflation >= INFLATION_MIN_VALUE
):
return (initial_yearly_inflation + unrestricted_end_of_year_inflation) / 2
if unrestricted_end_of_year_inflation > INFLATION_MAX_VALUE:
diff_to_max = INFLATION_MAX_VALUE - initial_yearly_inflation
max_point = diff_to_max / inflation_slope
avg_inflation = (
1 - max_point / 2
) * INFLATION_MAX_VALUE + max_point / 2 * initial_yearly_inflation
return avg_inflation
if unrestricted_end_of_year_inflation < INFLATION_MIN_VALUE:
diff_to_min = initial_yearly_inflation - INFLATION_MIN_VALUE
min_point = diff_to_min / inflation_slope
avg_inflation = (
1 - min_point / 2
) * INFLATION_MIN_VALUE + min_point / 2 * initial_yearly_inflation
return avg_inflation
raise RuntimeError("Unreachable code")
def rate(
*,
validator_commission: float,
bonded_ratio: float,
initial_inflation: float,
time_per_block: Seconds,
daily_fees: Atom,
total_supply: Atom,
) -> float:
"""
Compute the yearly rewards rate for a given validator at a given state of the reward chain.
Parameters :
- validator_commission : the commission the validator advertises
- bonded_ratio : the percentage of bonded tokens
- initial_inflation : the inflation at the beginning of the computation
- time_per_block : the actual average time per block on the blockchain
- daily_fees : the average amount of daily fees on the network
- total_supply : the total supply of Atom on the chain
Main assumptions / error-inducing hypotheses :
- The total_supply doesn't move during the year (at least daily_fees/total_supply ratio doesn't move)
- The bonded_ratio stays the same during the year
- The network parameters (CAPITALIZED_VARS) do not change during the year
- 1 uatom is exactly 1 unit of "voting power" (in reality, slashing changes that, but we ignore the difference it makes.)
"""
# This correction changes how inflation is computed vs. what the network advertises
inexact_block_time_correction = ASSUMED_TIME_PER_BLOCK / time_per_block
# This correction assumes a constant bonded_ratio, which gives a different yearly inflation as advertised
yearly_inflation = compute_avg_yearly_inflation(initial_inflation, bonded_ratio)
# This computation adds the fees to the rate computation
yearly_fee_rate = daily_fees * 365.24 / total_supply
return (
inexact_block_time_correction
* (yearly_inflation + yearly_fee_rate)
* 1
/ bonded_ratio
* (1 - POOL_COMMISSION)
* (1 - validator_commission)
)
def main(*_args, **kwargs):
"""
Main entrypoint to yearly rate computation
"""
# Dynamic parameters that affect the formula
commission = kwargs.get("validator_commission", 0)
bonded_ratio = kwargs.get("bonded_ratio", TARGET_BONDED_RATIO)
initial_inflation = kwargs.get("initial_inflation", 0.07)
time_per_block = kwargs.get("time_per_block", ASSUMED_TIME_PER_BLOCK)
daily_fees = kwargs.get("daily_fees", Atom(150))
total_supply = kwargs.get("total_supply", Atom(254000000))
yearly_rate = rate(
validator_commission=commission,
bonded_ratio=bonded_ratio,
initial_inflation=initial_inflation,
time_per_block=time_per_block,
daily_fees=daily_fees,
total_supply=total_supply,
)
print(
f"The yearly rate for a validator with {commission * 100}% commission is {round(yearly_rate * 100, 2)}%"
)
if __name__ == "__main__":
main(
validator_commission=0.04, # The higher the commission, the lower the rewards rate
bonded_ratio=0.7204, # The higher the bonded ratio, the lower the rewards rate
initial_inflation=0.07, # The higher the inflation, the higher the rewards rate
time_per_block=Seconds(
7
), # The higher the time per block, the lower the rewards rate
daily_fees=Atom(150), # The higher the daily fees, the higher the rewards rate
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment