oracle::validator_states
This module updates the state (and stats) for a validator when the protocol interacts with the validator viz.:
- creates a new delegation,
- pays commission,
- requests a withdrawal, or,
- when it reserves shares for payout.
This module also calculates the performance statistics of a validator. These statistics are used to calculate how much to delegate in a validator. Tortuga Protocol may start out with permissioned scoring and switch to permissionless scoring after sufficient data has been collected. Hence, this module may remain upgradable for longer than the rest of the protocol while the permissionless scoring is being fine-tuned.
This modules handles both permissioned and permissionless scoring.
use 0x1::error;
use 0x1::option;
use 0x1::reconfiguration;
use 0x1::signer;
use 0x1::timestamp;
use 0xc0ded0c0::circular_buffer;
use 0xc0ded0c0::iterable_table_custom;
use 0xc0ded0c0::math;
An observation of on online stats oracle. Effective reward rate for a validator is calculated after they have charged their commission, and after accounting for their performance degradations, like missed proposals.
struct Observation has copy, drop, store
timestamp
: u64
The time the observation was made.effective_reward_rate: u128
The effective reward rate of the validator at the time of the observation.This struct stores the balances and stats for an associated validator.
struct AssociatedValidator has store
last_update_timestamp: u64
Timestamp when the last checkpoint was made.last_reconfiguration_timestamp: u64
The last recorded reconfiguration timestamp from the aptos_framework
module.balance_at_last_update_unreserved: u64
Unreserved balance when the last update was made.balance_at_last_update_reserved: u64
Reserved balance when the last update was made.time_averaged_effective_reward_rate: u128
The time-averaged effective reward rate of the validator.observations:
circular_buffer::CircularBuffer
<
validator_states::Observation
>
Observations which will be used to calculate the effective reward.observation_begin_timestamp: u64
The earliest timestamp when an observation was made in the current buffer.score: u128
Score of the validatorpermissioned_score: u128
Permissioned score of the validator. It is used only when allow_permissionless_scoring
is set to false
.ramp_up_start_at: u64
Scoring may in future be ramped up starting from this timestamp.This struct stores the aggregate values across all validators associated with the protocol.
struct ValidatorSystem has key
total_balance_with_validators: u64
Total amount in APT in stakes with validatorstotal_unlocking_balance: u64
Total balance which has been reserved for payoutsassociated_validators:
iterable_table_custom::IterableTableCustom
<
address
,
validator_states::AssociatedValidator
>
Table of all associated validatorstotal_score: u128
Sum of scores of all the validatorsThis stores the on-chain oracle's config information. Careful choices for these values will be needed at genesis.
struct StatsConfig has key
initial_time_averaged_effective_reward_rate: u128
Initial time averaged effective reward rate to assign to a new validator.min_span_between_observations_sec: u64
Minimum time difference between reward rate checks.max_number_of_observations: u64
Max number of observations to keep track of.initial_delegation_target: u64
The initial amount of delegate to give to a validator. A nonzero value ensure that stats start to populate. *Note*: Should be around 10 APT.rate_normalizer: u128
The normalizer for the rate
of yield on the chain. If the chain reward rate is going to be ~ 10%
per annum, then effective_reward_rate
would be about 0.000011
per hour, or if we check validators every day and use the following normalize of 10^6
, reward rate would be at most a four digit number.time_normalizer: u128
The constant used to normalize the time. We will normalize observations to about 5 hours: 5 * 3600 = 18000.max_time_averaged_effective_reward_rate: u128
Maximum possible time averaged effective reward rate. this should equal:reward_rate * rate_normalizer * time_normalizer /
epoch_length
allow_permissionless_scoring: boolramp_up_duration: u64
This struct is generated when someone initializes this module. All the resources above are stored in the address specified here.
struct UpdateCapability has store
When the pool already exists on an address.
const EALREADY_EXISTS: u64 = 4;
The precision of the performance score.
const CLIFF_PRECISION: u128 = 10000;
When a request to reduce the number of observations the validator requires on-chain is made.
const ECANNOT_DECREASE_OBSERVATIONS: u64 = 3;
When
max_time_averaged_effective_reward_rate
is set to zero.const EMAX_TWAR_ZERO: u64 = 5;
When trying to remove a validator which had non-zero balance the last time it was updated.
const ENONZERO_BALANCE_AT_LAST_UPDATE: u64 = 2;
The precision for the score multiplier.
const MULTIPLIER_PRECISION: u64 = 10000;
This constant will act as total score if permissionless scoring is disabled
const PERMISSIONED_SCORING_NORMALIZER: u128 = 10000000000;
The bottom-performing validator cut-off.
const SCORE_BUCKET_BOT: u128 = 1000000;
The mid-performing validator cut-off.
const SCORE_BUCKET_MID: u128 = 100000000;
The top-performing validator cut-off.
const SCORE_BUCKET_TOP: u128 = 10000000000;
public fun initialize_validator_states(sender: &signer): validator_states::UpdateCapability
public fun initialize_validator_states(sender: &signer): UpdateCapability {
assert!(
!exists<ValidatorSystem>(signer::address_of(sender)),
error::already_exists(EALREADY_EXISTS)
);
move_to(sender, ValidatorSystem {
total_balance_with_validators: 0,
total_unlocking_balance: 0,
associated_validators:
iterable_table_custom::new<address, AssociatedValidator>(),
total_score: 0,
});
// The Aptos' reward rate will be about 0.0011 per hour
// `max_time_averaged_effective_reward_rate` should be calculated using
// this rate at genesis.
move_to(sender, StatsConfig {
// Validators start with 0 score.
initial_time_averaged_effective_reward_rate: 0,
// 5 hours
min_span_between_observations_sec: 5 * 3600,
// We keep track of reward rate for 500 hours (right now we can only
// increase this number after init)
max_number_of_observations: 10,
// Set the initial delegation target to 10 APT to kickoff stats
// calculations.
initial_delegation_target: 1000000000,
// Larger the `*_normalizer`, better the precision of the scores.
rate_normalizer: 1000000000000000, // large for better precision
time_normalizer: 3600 * 5, // 5*3600 five hours ; ideal genesis value
// TODO: May update.
// 0.0011 * 1000000000000000 * 3600*5 / 3600
// This must be > 0.
max_time_averaged_effective_reward_rate: 5500000000000,
// We start permissionless scoring sometime after the launch
allow_permissionless_scoring: false,
// We start with no ramp up
ramp_up_duration: 0,
});
UpdateCapability {
state_storage_address: signer::address_of(sender),
}
}
public fun get_total_unlocking_balance(state_storage_address: address): u64
Returns
validator_system.total_balance_with_validators
stored at the address state_storage_address
.public fun get_total_balance_with_validators(state_storage_address: address): u64
Returns the total balance with the managed staking pool at
managed_pool_address
.public fun get_balance_at_last_update(state_storage_address: address, managed_pool_address: address): u64
public fun get_balance_at_last_update(
state_storage_address: address,
managed_pool_address: address
): u64 acquires ValidatorSystem {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
validator.balance_at_last_update_reserved +
validator.balance_at_last_update_unreserved
}
This returns the target delegation that the managed staking pool at
managed_pool_address
should receive from the protocol, given the performance of the validator.This is used in module
stake_router
to allow permissionless staking. If allow_permissionless_scoring
is set to false
, the protocol determines the score that target_delegation
depends on.- If the
managed_pool_address
is not an associated validator.
public fun get_target_delegation(state_storage_address: address, managed_pool_address: address, required_tvl: u64): u64
public fun get_target_delegation(
state_storage_address: address,
managed_pool_address: address,
required_tvl: u64,
): u64 acquires ValidatorSystem, StatsConfig {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let stats_config = borrow_global<StatsConfig>(state_storage_address);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
if (stats_config.allow_permissionless_scoring) {
if (validator_system.total_score == 0) {
stats_config.initial_delegation_target
} else {
(
(
validator.score *
(required_tvl as u128) /
validator_system.total_score
) as u64
) + stats_config.initial_delegation_target
}
} else {
(
(
validator.permissioned_score *
(required_tvl as u128) /
PERMISSIONED_SCORING_NORMALIZER
) as u64
)
}
}
Get the score of the validator at
managed_pool_address
.- If the
managed_pool_address
is not an associated validator.
public fun get_score(state_storage_address: address, managed_pool_address: address): u128
public fun get_score(
state_storage_address: address,
managed_pool_address: address
): u128 acquires ValidatorSystem {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
validator.score
}
Get permissioned score of
managed_pool_address
from ValidatorSystem
stored at state_storage_address
.public fun get_permissioned_score(state_storage_address: address, managed_pool_address: address): u128
public fun get_permissioned_score(
state_storage_address: address,
managed_pool_address: address
): u128 acquires ValidatorSystem {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
validator.permissioned_score
}
Returns the time averaged effective reward rate of the validator at
managed_pool_address
from the ValidatorSystem
stored at the address state_storage_address
.public fun get_time_averaged_effective_reward_rate(state_storage_address: address, managed_pool_address: address): u128
public fun get_time_averaged_effective_reward_rate(
state_storage_address: address,
managed_pool_address: address
): u128 acquires ValidatorSystem {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
validator.time_averaged_effective_reward_rate
}
Returns the last time the validator at
managed_pool_address
was updated from the ValidatorSystem
stored at the address state_storage_address
.public fun get_last_update_timestamp(state_storage_address: address, managed_pool_address: address): u64
public fun get_last_update_timestamp(
state_storage_address: address,
managed_pool_address: address
): u64 acquires ValidatorSystem {
let validator_system = borrow_global<ValidatorSystem>(
state_storage_address
);
let validator =
iterable_table_custom::borrow<address, AssociatedValidator>(
&validator_system.associated_validators,
managed_pool_address
);
validator.last_update_timestamp
}
This function can be used to reset the
StatsConfig
configuration stored at the address update_cap.state_storage_address
post initialization.
StatsConfig
may be reset initially at launch to fine tune parameters. Config should not be changed after it has been fine tuned for a network.- If the
max_number_of_observations
is less than the currently set value.
public fun reset_config(update_cap: &validator_states::UpdateCapability, initial_time_averaged_effective_reward_rate: u128, min_span_between_observations_sec: u64, max_number_of_observations: u64, initial_delegation_target: u64, rate_normalizer: u128, time_normalizer: u128, max_time_averaged_effective_reward_rate: u128, allow_permissionless_scoring: bool, ramp_up_duration: u64)
public fun reset_config(
update_cap: &UpdateCapability,
initial_time_averaged_effective_reward_rate: u128,
min_span_between_observations_sec: u64,
max_number_of_observations: u64,
initial_delegation_target: u64,
rate_normalizer: u128,
time_normalizer: u128,
max_time_averaged_effective_reward_rate: u128,
allow_permissionless_scoring: bool,
ramp_up_duration: u64,
) acquires StatsConfig {
let stats_config = borrow_global_mut<StatsConfig>(
update_cap.state_storage_address
);
stats_config.initial_time_averaged_effective_reward_rate =
initial_time_averaged_effective_reward_rate;
stats_config.min_span_between_observations_sec =
min_span_between_observations_sec;
assert!(
max_number_of_observations >=
stats_config.max_number_of_observations,
error::invalid_argument(ECANNOT_DECREASE_OBSERVATIONS)
);
stats_config.max_number_of_observations = max_number_of_observations;
stats_config.initial_delegation_target = initial_delegation_target;
stats_config.rate_normalizer = rate_normalizer;
stats_config.time_normalizer = time_normalizer;
stats_config.allow_permissionless_scoring =
allow_permissionless_scoring;
// This is needed for `cliff` to not abort
assert!(
max_time_averaged_effective_reward_rate > 0,
error::invalid_argument(EMAX_TWAR_ZERO)
);
stats_config.max_time_averaged_effective_reward_rate =
max_time_averaged_effective_reward_rate;
stats_config.ramp_up_duration = ramp_up_duration;
}
Set score for the validator at
managed_pool_address
in the ValidatorSystem
stored at the address update_cap.state_storage_address
to value
.It would not affect
target_delegations
unless StatsConfig
.allow_permissionless_scoring
is set to false
.public fun set_permissioned_score(update_cap: &validator_states::UpdateCapability, managed_pool_address: address, value: u128)
public fun set_permissioned_score(
update_cap: &UpdateCapability,
managed_pool_address: address,
value: u128,
) acquires ValidatorSystem {
let validator_system = borrow_global_mut<ValidatorSystem>(
update_cap.state_storage_address
);
let validator =
iterable_table_custom::borrow_mut<address, AssociatedValidator>(
&mut validator_system.associated_validators,
managed_pool_address
);
validator.permissioned_score = value;
}
Add a new associated validator at
managed_pool_address
to the ValidatorSystem
stored at the address update_cap.state_storage_address
.public fun validator_signup_internal(managed_pool_address: address, update_cap: &validator_states::UpdateCapability)
public fun validator_signup_internal(
managed_pool_address: address,
update_cap: &UpdateCapability,
) acquires ValidatorSystem, StatsConfig {
let validator_system = borrow_global_mut<ValidatorSystem>(
update_cap.state_storage_address
);
let stats_config = borrow_global<StatsConfig>(
update_cap.state_storage_address
);
let current_time = timestamp::now_seconds();
// last reconfiguration time in seconds
let reconfiguration_time =
reconfiguration::last_reconfiguration_time() / 1000000;
let validator = AssociatedValidator {
last_update_timestamp: current_time,
last_reconfiguration_timestamp: reconfiguration_time,
balance_at_last_update_unreserved: 0,
balance_at_last_update_reserved: 0,
time_averaged_effective_reward_rate:
stats_config.initial_time_averaged_effective_reward_rate,
observations: circular_buffer::empty<Observation>(),
observation_begin_timestamp: current_time,
score: 0,
permissioned_score: PERMISSIONED_SCORING_NORMALIZER,
ramp_up_start_at: current_time,
};
iterable_table_custom::add<address, AssociatedValidator>(
&mut validator_system.associated_validators,
managed_pool_address, validator
);
}
Remove an associated validator at
managed_pool_address
from the ValidatorSystem
stored at the address update_cap.state_storage_address
.- If either the reserved or unreserved balance with the validator is non-zero.
public fun validator_removal_internal(managed_pool_address: address, update_cap: &validator_states::UpdateCapability)
public fun validator_removal_internal(
managed_pool_address: address,
update_cap: &UpdateCapability,
) acquires ValidatorSystem {
let validator_system = borrow_global_mut<ValidatorSystem>(
update_cap.state_storage_address
);
let validator =
iterable_table_custom::remove<address, AssociatedValidator>(
&mut validator_system.associated_validators,
managed_pool_address
);
// The balances should be able to be reset permissionlessly via
// `validator_router::pay_commission_to_the_validator`.
assert!(
validator.balance_at_last_update_unreserved == 0 &&
validator.balance_at_last_update_reserved == 0,
error::invalid_state(ENONZERO_BALANCE_AT_LAST_UPDATE)
);
burn_associated_validator(validator);
}
This function is called at every interaction with an associated validator, to update balances and stats. Commissions must be paid to the associated validator before calling this function.
public fun update_validator_and_total_internal(managed_pool_address: address, update_cap: &validator_states::UpdateCapability, current_balance_reserved: u64, current_balance_unreserved: u64, amount_in: u64, amount_out: u64, amount_reserved: u64, validator_specific_scoring_multiplier: u64, shift_amount: u64): option::Option<u128>
public fun update_validator_and_total_internal(
managed_pool_address: address,
update_cap: &UpdateCapability,
current_balance_reserved: u64,
current_balance_unreserved: u64,
amount_in: u64,
amount_out: u64,
amount_reserved: u64,
validator_specific_scoring_multiplier: u64,
shift_amount: u64,
): Option<u128> acquires ValidatorSystem, StatsConfig {
// MODIFY THE BALANCES FIRST
// only at most one of the amounts field could be nonzero:
// 1. `amount_in > 0` if the money just went into the
// `managed_stake_pool` just before state modification via a delegate
// call,
// 2. `amount_out > 0` if the money just went out of the
// `managed_stake_pool` just before state modification via a
// `withdraw_to_reserve` call,
// 3. `amount_reserved > 0` if an amount was reserved for withdrawal in
// the `managed_stake_pool` just before state modification via a
// `reserve_shares_for_payout` call, or
// 4. all three amount fields are zero, if only
// `pay_commission_to_owner` was called
// We calculate current balances
let validator_system = borrow_global_mut<ValidatorSystem>(
update_cap.state_storage_address
);
let validator =
iterable_table_custom::borrow_mut<address, AssociatedValidator>(
&mut validator_system.associated_validators,
managed_pool_address
);
let current_balance =
current_balance_reserved + current_balance_unreserved;
let balance_at_last_update =
validator.balance_at_last_update_reserved +
validator.balance_at_last_update_unreserved;
let (
total_rewards,
total_negative_rewards,
reserved_rewards,
reserved_negative_rewards,
) = calculate_rewards(
current_balance,
balance_at_last_update,
current_balance_reserved,
validator.balance_at_last_update_reserved,
amount_in,
amount_out,
amount_reserved,
);
if (amount_in > 0) {
// we now calculate the rewards on balance which is unlocking
// We use if else to avoid any floating point catastrophe
validator_system.total_balance_with_validators =
math::add_possibly_negative(
validator_system.total_balance_with_validators + amount_in,
total_rewards,
total_negative_rewards
);
validator_system.total_unlocking_balance =
math::add_possibly_negative(
validator_system.total_unlocking_balance,
reserved_rewards,
reserved_negative_rewards
);
} else if (amount_out > 0) {
// The order of `safe_sub` and `add_possibly_negative` is important
// here. Reversing it can result in a nonzero entry for
// `total_balance_with_validators`, while it should actually be
// zero.
validator_system.total_balance_with_validators = math::safe_sub(
math::add_possibly_negative(
validator_system.total_balance_with_validators,
total_rewards,
total_negative_rewards
),
amount_out
);
// It is important to not subtract `amount_out` as the old state
// did not know about amount_out
validator_system.total_unlocking_balance = math::safe_sub(
validator_system.total_unlocking_balance,
validator.balance_at_last_update_reserved
);
} else {
validator_system.total_balance_with_validators =
math::add_possibly_negative(
validator_system.total_balance_with_validators,
total_rewards,
total_negative_rewards
);
validator_system.total_unlocking_balance =
math::add_possibly_negative(
validator_system.total_unlocking_balance + amount_reserved,
reserved_rewards,
reserved_negative_rewards
);
};
validator.balance_at_last_update_reserved = current_balance_reserved;
validator.balance_at_last_update_unreserved =
current_balance_unreserved;
// Finally, we update the validator stats
let stats_config = borrow_global<StatsConfig>(
update_cap.state_storage_address
);
let observation_option = modify_validator_score(
stats_config,
validator_system,
managed_pool_address,
balance_at_last_update,
total_rewards,
validator_specific_scoring_multiplier,
shift_amount,
);
// Return a value
if (option::is_none(&observation_option)) {
option::destroy_none(observation_option);
option::none()
} else {
let a = option::destroy_some(observation_option);
option::some(a.effective_reward_rate)
}
}
Returns the
total_rewards, total_negative_rewards, reserved_rewards, reserved_negative_rewards
given the current and previous balances and the amount of tokens that were added or removed.fun calculate_rewards(current_balance: u64, balance_at_last_update: u64, current_balance_reserved: u64, balance_at_last_update_reserved: u64, amount_in: u64, amount_out: u64, amount_reserved: u64): (u64, u64, u64, u64)
fun calculate_rewards(
current_balance: u64,
balance_at_last_update: u64,
current_balance_reserved: u64,
balance_at_last_update_reserved: u64,
amount_in: u64,
amount_out: u64,
amount_reserved: u64,
): (u64, u64, u64, u64) {
let total_rewards = math::safe_sub(
current_balance + amount_out,
balance_at_last_update + amount_in
);
let total_negative_rewards = math::safe_sub(
balance_at_last_update + amount_in,
current_balance + amount_out
);
let reserved_rewards = math::safe_sub(
current_balance_reserved,
balance_at_last_update_reserved + amount_reserved
);
let reserved_negative_rewards = math::safe_sub(
balance_at_last_update_reserved + amount_reserved,
current_balance_reserved
);
(
total_rewards,
total_negative_rewards,
reserved_rewards,
reserved_negative_rewards
)
}
Calculates effective reward rate based on, new
rewards
, balance_at_last_update
and time_delta
between the updates.The
stats_config
is needed to determine the normalizers.fun effective_reward_rate(stats_config: &validator_states::StatsConfig, rewards: u128, balance_at_last_update: u128, time_delta: u128): u128
fun effective_reward_rate(
stats_config: &StatsConfig,
rewards: u128,
balance_at_last_update: u128,
time_delta: u128,
): u128 {
// The order of operations is important to ensure highest precision.
(rewards * stats_config.rate_normalizer / balance_at_last_update) *
stats_config.time_normalizer / time_delta
}
Returns the time averaged effective reward rate given the
tail_sum
, the new head
and the total_time
span the observations cover.fun time_averaged_effective_reward_rate(tail_sum: u128, head: u128, total_time: u128): u128
Calculate scoring multiplier for the validator.
fun calc_ramp_up_multiplier(ramp_up_duration: u64, ramp_up_start_at: u64, current_timestamp: u64): u64
fun calc_ramp_up_multiplier(
ramp_up_duration: u64,
ramp_up_start_at: u64,
current_timestamp: u64,
): u64 {
if (
current_timestamp >= ramp_up_start_at + ramp_up_duration
|| ramp_up_duration == 0
) {
MULTIPLIER_PRECISION
} else {
math::mul_div(
math::safe_sub(
current_timestamp,
ramp_up_start_at
),
MULTIPLIER_PRECISION,
ramp_up_duration
)
}
}
Modifies the stats for the validator at
managed_pool_address
given the balance_at_last_update
, and rewards
.This also updates the
validator_system
with the new score
for the validator.fun modify_validator_score(stats_config: &validator_states::StatsConfig, validator_system: &mut validator_states::ValidatorSystem, managed_pool_address: address, balance_at_last_update: u64, rewards: u64, twar_multiplier: u64, shift_amount: u64): option::Option<validator_states::Observation>
fun modify_validator_score(
stats_config: &StatsConfig,
validator_system: &mut ValidatorSystem,
managed_pool_address: address,
balance_at_last_update: u64,
rewards: u64,
twar_multiplier: u64,
shift_amount: u64,
): Option<Observation> {
let current_timestamp = timestamp::now_seconds();
// Modify the stats
let reconfiguration_timestamp =
reconfiguration::last_reconfiguration_time() / 1000000;
let validator =
iterable_table_custom::borrow_mut<address, AssociatedValidator>(
&mut validator_system.associated_validators,
managed_pool_address
);
let observations_length = circular_buffer::length<Observation>(
&validator.observations
);
let last_observation_timestamp = if (observations_length > 0) {
circular_buffer::borrow_newest<Observation>(
&validator.observations
).timestamp
} else {
0
};
let time_since_last_observation =
reconfiguration_timestamp - last_observation_timestamp;
let reconfiguration_delta =
reconfiguration_timestamp -
validator.last_reconfiguration_timestamp;
if (
reconfiguration_delta == 0 ||
time_since_last_observation <
stats_config.min_span_between_observations_sec ||
balance_at_last_update == 0
) {
// We don't modify stats if the balance was zero in the validator,
// so as to not punish them for not having any balance there are no
// rewards in this case, so we can short cut early
validator.last_update_timestamp = current_timestamp;
validator.last_reconfiguration_timestamp =
reconfiguration_timestamp;
if (
balance_at_last_update == 0 &&
circular_buffer::length<Observation>(
&validator.observations
) == 0
) {
// validator.observation_begin_timestamp = current_timestamp;
validator.observation_begin_timestamp =
reconfiguration_timestamp;
};
return option::none<Observation>()
};
// calculate the time_averaged_effective_reward_rate
let effective_reward_rate = effective_reward_rate(
stats_config,
(rewards as u128),
(balance_at_last_update as u128),
(reconfiguration_delta as u128)
);
let current_observation = Observation {
timestamp: reconfiguration_timestamp,
effective_reward_rate: effective_reward_rate,
};
let time_span = if (observations_length > 0) {
(
(
last_observation_timestamp -
validator.observation_begin_timestamp
) as u128
)
} else {
(0 as u128)
};
let time_delta_u128 = if (observations_length > 0) {
((reconfiguration_timestamp - last_observation_timestamp) as u128)
} else {
(
(
reconfiguration_timestamp -
validator.observation_begin_timestamp
) as u128
)
};
let total_time = time_span + time_delta_u128;
let head = effective_reward_rate * time_delta_u128;
let tail_sum =
validator.time_averaged_effective_reward_rate * time_span;
if (observations_length < stats_config.max_number_of_observations) {
validator.time_averaged_effective_reward_rate =
time_averaged_effective_reward_rate(
tail_sum,
head,
total_time,
);
} else {
let oldest_observation =
circular_buffer::borrow_oldest<Observation>(
&validator.observations
);
let oldest_time_delta = (
(
oldest_observation.timestamp -
validator.observation_begin_timestamp
) as u128
);
validator.observation_begin_timestamp =
oldest_observation.timestamp;
let new_tail_sum = math::safe_sub_u128(
tail_sum,
oldest_observation.effective_reward_rate * oldest_time_delta
);
let new_total_time = total_time - oldest_time_delta;
validator.time_averaged_effective_reward_rate =
time_averaged_effective_reward_rate(
new_tail_sum,
head,
new_total_time,
);
};
// add the new observation
circular_buffer::push<Observation>(
&mut validator.observations,
copy current_observation,
stats_config.max_number_of_observations
);
// update scores
let old_score = validator.score;
let ramp_up_multiplier = calc_ramp_up_multiplier(
stats_config.ramp_up_duration,
validator.ramp_up_start_at,
current_timestamp
);
validator.score = cliff(
validator.time_averaged_effective_reward_rate,