From 097c44a53a6ae5e56856c2bfbe805d45fa5c8f4a Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 24 May 2023 16:36:58 +0200 Subject: [PATCH 1/7] Proto: Add Storage for dynamic rewards --- src/proto_alpha/lib_protocol/storage.ml | 10 ++++++++++ src/proto_alpha/lib_protocol/storage.mli | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index 910eb91ac896..9986d6fcd665 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1108,6 +1108,15 @@ module Cycle = struct let encoding = Sampler.encoding Raw_context.consensus_pk_encoding end) + (* Unit = 1_000_000_000_000_000L, defined in [adaptive_inflation_storage.ml] *) + module Reward_bonus = + Indexed_context.Make_map + (Registered) + (struct + let name = ["reward_bonus"] + end) + (Encoding.Int64) + module Reward_coeff = Indexed_context.Make_map (Registered) @@ -1248,6 +1257,7 @@ module Stake = struct end module Delegate_sampler_state = Cycle.Delegate_sampler_state +module Reward_bonus = Cycle.Reward_bonus module Reward_coeff = Cycle.Reward_coeff (** Votes *) diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 20e431e8e4c1..2538e0e10711 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -493,7 +493,15 @@ module Delegate_sampler_state : and type value = Raw_context.consensus_pk Sampler.t and type t := Raw_context.t -(** Multiplicative coefficient for rewards under Adaptive Inflation *) +(** Compounding reward bonus for Adaptive Inflation *) +module Reward_bonus : + Indexed_data_storage + with type key = Cycle_repr.t + and type value = Int64.t + and type t := Raw_context.t + +(** Multiplicative coefficient for rewards under Adaptive Inflation + (Includes the bonus) *) module Reward_coeff : Indexed_data_storage with type key = Cycle_repr.t -- GitLab From 3266e25d5f77eba050504b57813ff85067ffb73d Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Fri, 26 May 2023 10:46:27 +0200 Subject: [PATCH 2/7] Proto: clear outdated reward bonus --- src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index 1baeed660054..239f4ca2e1f5 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -91,9 +91,12 @@ let compute_and_store_reward_coeff_at_cycle_end ctxt ~new_cycle = return ctxt let clear_outdated_reward_data ctxt ~new_cycle = + let open Lwt_syntax in match Cycle_repr.sub new_cycle 1 with | None -> Lwt.return ctxt - | Some cycle -> Storage.Reward_coeff.remove ctxt cycle + | Some cycle -> + let* ctxt = Storage.Reward_coeff.remove ctxt cycle in + Storage.Reward_bonus.remove ctxt cycle let update_stored_rewards_at_cycle_end ctxt ~new_cycle = let open Lwt_result_syntax in -- GitLab From 4fd64406b2d1e926e2122158db8e6640a2c7e8f8 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 24 May 2023 17:00:36 +0200 Subject: [PATCH 3/7] Proto: get reward bonus --- .../adaptive_inflation_storage.ml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index 239f4ca2e1f5..a930484e0b12 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -25,7 +25,10 @@ (* Default reward coefficient when AI is not in effect, chosen so that rewards * coeff = rewards *) -let default = Q.one +let default_reward = Q.one + +(* Default bonus value *) +let default_bonus = 0L let get_reward_coeff ctxt ~cycle = let open Lwt_result_syntax in @@ -34,8 +37,19 @@ let get_reward_coeff ctxt ~cycle = (* Even if AI is enabled, the storage can be empty: this is the case for the first 5 cycles after AI is enabled *) let* k_opt = Storage.Reward_coeff.find ctxt cycle in - return (Option.value ~default k_opt) - else return default + return (Option.value ~default:default_reward k_opt) + else return default_reward + +let get_reward_bonus ctxt ~cycle = + let open Lwt_result_syntax in + match cycle with + | None -> return default_bonus + | Some cycle -> + let ai_enable = (Raw_context.constants ctxt).adaptive_inflation.enable in + if ai_enable then + let* k_opt = Storage.Reward_bonus.find ctxt cycle in + return (Option.value ~default:default_bonus k_opt) + else return default_bonus let load_reward_coeff ctxt ~cycle = let open Lwt_result_syntax in -- GitLab From 26a15c1c6944ce7ae7b6e524c1c344deafc7f8ea Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 24 May 2023 17:01:20 +0200 Subject: [PATCH 4/7] Proto/AI: add dummy compute bonus --- src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index a930484e0b12..21acda638b56 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -59,6 +59,12 @@ let load_reward_coeff ctxt ~cycle = in return ctxt +let compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus = + ignore total_supply ; + ignore total_frozen_stake ; + ignore previous_bonus ; + default_bonus + let compute_coeff = let q_400 = Q.of_int 400 in let q_min_per_year = Q.of_int 525600 in -- GitLab From 506e39f66031529e8fe66646576175e2cd0048d8 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 24 May 2023 17:01:37 +0200 Subject: [PATCH 5/7] Proto/AI: add reward bonus to storage and coeff --- .../lib_protocol/adaptive_inflation_storage.ml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index 21acda638b56..b83faab8009d 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -30,6 +30,10 @@ let default_reward = Q.one (* Default bonus value *) let default_bonus = 0L +(* Order of magnitude of the total supply in mutez + Approximately 2^50 *) +let bonus_unit = 1_000_000_000_000_000L + let get_reward_coeff ctxt ~cycle = let open Lwt_result_syntax in let ai_enable = (Raw_context.constants ctxt).adaptive_inflation.enable in @@ -68,7 +72,7 @@ let compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus = let compute_coeff = let q_400 = Q.of_int 400 in let q_min_per_year = Q.of_int 525600 in - fun ~base_total_rewards_per_minute ~total_supply ~total_frozen_stake -> + fun ~base_total_rewards_per_minute ~total_supply ~total_frozen_stake ~bonus -> let q_total_supply = Tez_repr.to_mutez total_supply |> Q.of_int64 in let q_total_frozen_stake = Tez_repr.to_mutez total_frozen_stake |> Q.of_int64 @@ -76,11 +80,13 @@ let compute_coeff = let q_base_total_rewards_per_minute = Tez_repr.to_mutez base_total_rewards_per_minute |> Q.of_int64 in + let q_bonus = Q.(div (of_int64 bonus) (of_int64 bonus_unit)) in let x = Q.div q_total_frozen_stake q_total_supply (* = portion of frozen stake *) in let inv_f = Q.(mul (mul x x) q_400) in let f = Q.inv inv_f (* f = 1/400 * (1/x)^2 = yearly inflation rate *) in + let f = Q.add f q_bonus in (* f is truncated so that 0.5% <= f <= 10% *) let f = Q.(min f (1 // 10)) in let f = Q.(max f (5 // 1000)) in @@ -95,18 +101,25 @@ let compute_and_store_reward_coeff_at_cycle_end ctxt ~new_cycle = else let preserved = Constants_storage.preserved_cycles ctxt in let for_cycle = Cycle_repr.add new_cycle preserved in + let before_for_cycle = Cycle_repr.pred for_cycle in let* total_supply = Storage.Contract.Total_supply.get ctxt in let* total_stake = Stake_storage.get_total_active_stake ctxt for_cycle in let base_total_rewards_per_minute = (Constants_storage.reward_weights ctxt).base_total_rewards_per_minute in let total_frozen_stake = Stake_repr.get_frozen total_stake in + let* previous_bonus = get_reward_bonus ctxt ~cycle:before_for_cycle in + let bonus = + compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus + in let coeff = compute_coeff ~base_total_rewards_per_minute ~total_supply ~total_frozen_stake + ~bonus in + let*! ctxt = Storage.Reward_bonus.add ctxt for_cycle bonus in let*! ctxt = Storage.Reward_coeff.add ctxt for_cycle coeff in return ctxt -- GitLab From 686e7454f7c62a682d72ed35c6adc2c6726e22ad Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Thu, 1 Jun 2023 15:13:45 +0200 Subject: [PATCH 6/7] Proto/AI: minor refactor --- .../adaptive_inflation_storage.ml | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index b83faab8009d..cc2985340b3e 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -30,6 +30,10 @@ let default_reward = Q.one (* Default bonus value *) let default_bonus = 0L +let reward_ratio_min = Q.(5 // 1000) + +let reward_ratio_max = Q.(1 // 10) + (* Order of magnitude of the total supply in mutez Approximately 2^50 *) let bonus_unit = 1_000_000_000_000_000L @@ -63,6 +67,18 @@ let load_reward_coeff ctxt ~cycle = in return ctxt +let compute_reward_coeff_ratio = + let q_400 = Q.of_int 400 in + fun ~stake_ratio ~bonus -> + let q_bonus = Q.(div (of_int64 bonus) (of_int64 bonus_unit)) in + let inv_f = Q.(mul (mul stake_ratio stake_ratio) q_400) in + let f = Q.inv inv_f (* f = 1/400 * (1/x)^2 = yearly inflation rate *) in + let f = Q.add f q_bonus in + (* f is truncated so that 0.5% <= f <= 10% *) + let f = Q.(min f reward_ratio_max) in + let f = Q.(max f reward_ratio_min) in + f + let compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus = ignore total_supply ; ignore total_frozen_stake ; @@ -70,26 +86,19 @@ let compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus = default_bonus let compute_coeff = - let q_400 = Q.of_int 400 in let q_min_per_year = Q.of_int 525600 in fun ~base_total_rewards_per_minute ~total_supply ~total_frozen_stake ~bonus -> let q_total_supply = Tez_repr.to_mutez total_supply |> Q.of_int64 in let q_total_frozen_stake = Tez_repr.to_mutez total_frozen_stake |> Q.of_int64 in + let stake_ratio = + Q.div q_total_frozen_stake q_total_supply (* = portion of frozen stake *) + in let q_base_total_rewards_per_minute = Tez_repr.to_mutez base_total_rewards_per_minute |> Q.of_int64 in - let q_bonus = Q.(div (of_int64 bonus) (of_int64 bonus_unit)) in - let x = - Q.div q_total_frozen_stake q_total_supply (* = portion of frozen stake *) - in - let inv_f = Q.(mul (mul x x) q_400) in - let f = Q.inv inv_f (* f = 1/400 * (1/x)^2 = yearly inflation rate *) in - let f = Q.add f q_bonus in - (* f is truncated so that 0.5% <= f <= 10% *) - let f = Q.(min f (1 // 10)) in - let f = Q.(max f (5 // 1000)) in + let f = compute_reward_coeff_ratio ~stake_ratio ~bonus in let f = Q.div f q_min_per_year (* = inflation per minute *) in let f = Q.mul f q_total_supply (* = rewards per minute *) in Q.div f q_base_total_rewards_per_minute -- GitLab From 43b2cf5b01a57c4bda8d7e1035502bbb6230b45b Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Thu, 1 Jun 2023 16:11:54 +0200 Subject: [PATCH 7/7] Proto/AI: implement bonus reward --- .../adaptive_inflation_storage.ml | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml index cc2985340b3e..13c913255d0b 100644 --- a/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml +++ b/src/proto_alpha/lib_protocol/adaptive_inflation_storage.ml @@ -38,6 +38,10 @@ let reward_ratio_max = Q.(1 // 10) Approximately 2^50 *) let bonus_unit = 1_000_000_000_000_000L +let ratio_to_bonus q = Q.(q * of_int64 bonus_unit |> to_int64) + +let max_bonus = Int64.div bonus_unit 20L (* = 5% *) + let get_reward_coeff ctxt ~cycle = let open Lwt_result_syntax in let ai_enable = (Raw_context.constants ctxt).adaptive_inflation.enable in @@ -79,11 +83,48 @@ let compute_reward_coeff_ratio = let f = Q.(max f reward_ratio_min) in f -let compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus = - ignore total_supply ; - ignore total_frozen_stake ; - ignore previous_bonus ; - default_bonus +let compute_bonus = + let growth_rate = + 115_740_740L + (* = 0.01 * [bonus_unit] / second_per_day + For each % and each day, grows the bonus by 0.01% *) + in + (* Parameters for the dead zone: the bonus should not change in the interval [48%,52%] *) + let center_dz = Q.(1 // 2) (* = 50 % *) in + let radius_dz = Q.(1 // 50) (* = 2% *) in + fun ~seconds_per_cycle ~total_supply ~total_frozen_stake ~previous_bonus -> + let q_total_supply = Tez_repr.to_mutez total_supply |> Q.of_int64 in + let q_total_frozen_stake = + Tez_repr.to_mutez total_frozen_stake |> Q.of_int64 + in + let stake_ratio = + Q.div q_total_frozen_stake q_total_supply (* = portion of frozen stake *) + in + let base_reward_coeff_ratio = + compute_reward_coeff_ratio ~stake_ratio ~bonus:0L + in + let base_reward_coeff_dist_to_max = + ratio_to_bonus Q.(reward_ratio_max - base_reward_coeff_ratio) + in + (* The bonus reward is truncated between [0] and [max_bonus] *) + (* It is done in a way that the bonus does not increase if the coeff + would already be above the [reward_pct_max] *) + let max_bonus = Compare.Int64.min base_reward_coeff_dist_to_max max_bonus in + (* [dist] is the distance from [stake_ratio] to [48%,52%] *) + let unsigned_dist = + Q.(max zero (abs (stake_ratio - center_dz) - radius_dz)) + in + let dist_q = + if Compare.Q.(stake_ratio >= center_dz) then Q.neg unsigned_dist + else unsigned_dist + in + let dist = ratio_to_bonus dist_q in + let new_bonus = + Int64.(add previous_bonus (mul dist (mul growth_rate seconds_per_cycle))) + in + let new_bonus = Compare.Int64.max new_bonus 0L in + let new_bonus = Compare.Int64.min new_bonus max_bonus in + new_bonus let compute_coeff = let q_min_per_year = Q.of_int 525600 in @@ -118,8 +159,19 @@ let compute_and_store_reward_coeff_at_cycle_end ctxt ~new_cycle = in let total_frozen_stake = Stake_repr.get_frozen total_stake in let* previous_bonus = get_reward_bonus ctxt ~cycle:before_for_cycle in + let blocks_per_cycle = + Constants_storage.blocks_per_cycle ctxt |> Int64.of_int32 + in + let minimal_block_delay = + Constants_storage.minimal_block_delay ctxt |> Period_repr.to_seconds + in + let seconds_per_cycle = Int64.mul blocks_per_cycle minimal_block_delay in let bonus = - compute_bonus ~total_supply ~total_frozen_stake ~previous_bonus + compute_bonus + ~seconds_per_cycle + ~total_supply + ~total_frozen_stake + ~previous_bonus in let coeff = compute_coeff -- GitLab