Skip to content

Instantly share code, notes, and snippets.

@djrtwo
Last active April 12, 2018 02:22
Show Gist options
  • Save djrtwo/2292abf03cf6ec0c4ea458c5dce39983 to your computer and use it in GitHub Desktop.
Save djrtwo/2292abf03cf6ec0c4ea458c5dce39983 to your computer and use it in GitHub Desktop.
script used to model rewards in casper FFG
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()
@djrtwo
Copy link
Author

djrtwo commented Apr 12, 2018

Example command line usage:
python reward.py 5.0 27 --offline 0.50
python reward.py --help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment