Last active
April 12, 2018 02:22
-
-
Save djrtwo/2292abf03cf6ec0c4ea458c5dce39983 to your computer and use it in GitHub Desktop.
script used to model rewards in casper FFG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import math | |
from copy import copy | |
def sqrt_of_total_deposits(deposits): | |
return math.sqrt(deposits) | |
def reward_vote(deposit, reward_factor): | |
return deposit * (1 + reward_factor) | |
def collective_reward(deposits, vote_fraction, reward_factor): | |
return [ | |
deposit * (1 + vote_fraction * reward_factor / 2) / (1 + reward_factor) | |
for deposit in deposits | |
] | |
def update_reward_factor(total_deposits, esf, base_interest_factor, base_penalty_factor): | |
return base_interest_factor / sqrt_of_total_deposits(total_deposits) \ | |
+ base_penalty_factor * esf | |
def calculate_annual_interest(initial_deposits, fraction_vote, base_interest_factor, base_penalty_factor): | |
# 1/14.0 (blocks/second) * 86400 (seconds/day) * 365 (days/year) * 1/50 (epochs/block) | |
epochs_per_year = (86400 * 365) / (14 * 50) | |
reward_factor = 0 | |
deposits = copy(initial_deposits) | |
for epoch in range(epochs_per_year): | |
num_voted = int(len(deposits) * fraction_vote) | |
deposits = collective_reward(deposits, fraction_vote, reward_factor) | |
deposits = [reward_vote(deposit, reward_factor) for deposit in deposits[:num_voted]] | |
reward_factor = update_reward_factor( | |
sum(deposits), | |
2, | |
base_interest_factor, | |
base_penalty_factor | |
) | |
initial_total = sum(initial_deposits) | |
end_total = sum(deposits) | |
issuance = end_total - initial_total | |
interest = issuance / initial_total | |
percent_gain = interest * 100 | |
print("interest_factor:\t%f" % base_interest_factor) | |
print("initial:\t\t%s" % initial_total) | |
print("end:\t\t\t%s" % end_total) | |
print("issuance:\t\t%s" % issuance) | |
print("interest:\t\t%.2f%%" % percent_gain) | |
print("") | |
return percent_gain | |
def calculate_validator_half_life(initial_deposits, fraction_offline, base_interest_factor, base_penalty_factor): | |
split_index = int(len(initial_deposits) * (1 - fraction_offline)) | |
voting = initial_deposits[:split_index] | |
offline = initial_deposits[split_index:] | |
initial_offline_total = sum(offline) | |
reward_factor = 0.0 | |
esf = 2 | |
epoch_count = 0 | |
while sum(offline) > 0.5 * initial_offline_total: | |
fraction_voted = sum(voting) / float((sum(voting + offline))) | |
voting = collective_reward(voting, fraction_voted, reward_factor) | |
offline = collective_reward(offline, fraction_voted, reward_factor) | |
voting = [reward_vote(deposit, reward_factor) for deposit in voting] | |
if fraction_voted >= 2/3.0: | |
esf = 2 | |
else: | |
esf += 1 | |
reward_factor = update_reward_factor( | |
sum(voting + offline), | |
esf, | |
base_interest_factor, | |
base_penalty_factor | |
) | |
epoch_count += 1 | |
return epoch_count | |
def calculate_interest_factor(target_annual_interest, initial_deposits): | |
interest_factor = 0.01 | |
while True: | |
percent_gain = calculate_annual_interest(initial_deposits, 1.0, interest_factor, 0) | |
if percent_gain > (target_annual_interest * 0.97) and percent_gain < (target_annual_interest * 1.03): | |
return interest_factor | |
if percent_gain > target_annual_interest: | |
interest_factor = interest_factor * 0.9 | |
else: | |
interest_factor = interest_factor * 1.1 | |
def calculate_penalty_factor(target_day_half_life, initial_deposits, | |
interest_factor, fraction_offline): | |
penalty_factor = 0.0005 | |
while True: | |
epoch_half_life = calculate_validator_half_life(initial_deposits, fraction_offline, | |
interest_factor, penalty_factor) | |
# 1/14.0 (blocks/second) * 86400 (seconds/day) * 1/50 (epochs/block) | |
epochs_per_day = 86400 / (14.0 * 50) | |
day_half_life = epoch_half_life / epochs_per_day | |
print("penalty:\t%.6f" % penalty_factor) | |
print("epochs:\t\t%s" % epoch_half_life) | |
print("days:\t\t%.2f" % day_half_life) | |
print("") | |
if day_half_life > (target_day_half_life * 0.95) and day_half_life < (target_day_half_life * 1.05): | |
return penalty_factor | |
if day_half_life > target_day_half_life: | |
penalty_factor = penalty_factor * 1.2 | |
else: | |
penalty_factor = penalty_factor * 0.8 | |
def main(): | |
parser = argparse.ArgumentParser(description='Run FFG reward script.') | |
parser.add_argument( | |
'target_interest', type=float, | |
help='the target annual interest as a percent' | |
) | |
parser.add_argument( | |
'target_half_life', type=int, | |
help='the target half_life of inactive validators in days' | |
) | |
parser.add_argument( | |
'--offline', type=float, default=0.5, | |
help='the percentage of validators that go offline for penalty calculation' | |
) | |
parser.add_argument( | |
'--total-deposits', type=float, default=10000000, | |
help='the total initial deposits' | |
) | |
args = parser.parse_args() | |
num_validators = 100 | |
deposit_size = args.total_deposits / num_validators | |
initial_deposits = [deposit_size for i in range(num_validators)] # target 10 million ether | |
assert args.total_deposits == sum(initial_deposits) | |
interest_factor = calculate_interest_factor(args.target_interest, initial_deposits) | |
penalty_factor = calculate_penalty_factor(args.target_half_life, initial_deposits, interest_factor, args.offline) | |
print("interest_factor:\t%f" % interest_factor) | |
print("penalty_factor:\t\t%f" % penalty_factor) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example command line usage:
python reward.py 5.0 27 --offline 0.50
python reward.py --help