Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MASP rewards controller using token amounts instead of ratios #2460

Merged
merged 7 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- MASP inflation for a given token now is adjusted based on a target amount
of total locked (shielded) tokens rather than a ratio relative to some total
supply. ([\#2460](https://github.com/anoma/namada/pull/2460))
6 changes: 3 additions & 3 deletions crates/apps/src/lib/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2523,7 +2523,7 @@ pub async fn query_masp_reward_tokens(context: &impl Namada) {
max_reward_rate,
kp_gain,
kd_gain,
locked_ratio_target,
locked_amount_target,
} in tokens
{
display_line!(context.io(), "{}: {}", name, address);
Expand All @@ -2532,8 +2532,8 @@ pub async fn query_masp_reward_tokens(context: &impl Namada) {
display_line!(context.io(), " Kd gain: {}", kd_gain);
display_line!(
context.io(),
" Locked ratio target: {}",
locked_ratio_target
" Locked amount target: {}",
locked_amount_target
);
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/apps/src/lib/node/ledger/shell/init_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ where
masp_params,
&mut self.wl_storage,
address,
denom,
)
.unwrap();
if masp_params.is_some() {
Expand Down
168 changes: 121 additions & 47 deletions crates/core/src/ledger/inflation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,24 @@
use crate::types::dec::Dec;
use crate::types::uint::Uint;

/// The domains of inflation
pub enum RewardsType {
/// Proof-of-stake rewards
Staking,
/// Rewards for locking tokens in the multi-asset shielded pool
Masp,
/// Rewards for public goods funding (PGF)
PubGoodsFunding,
/// Holds the PD controller values that should be updated in storage
#[allow(missing_docs)]
pub struct PosValsToUpdate {
pub locked_ratio: Dec,
pub inflation: Uint,
}

/// Holds the PD controller values that should be updated in storage
#[allow(missing_docs)]
pub struct ValsToUpdate {
pub locked_ratio: Dec,
pub struct ShieldedValsToUpdate {
pub inflation: Uint,
}

/// PD controller used to dynamically adjust the rewards rates
#[derive(Debug, Clone)]
pub struct RewardsController {
pub struct PosRewardsController {
/// Locked token amount in the relevant system
pub locked_tokens: Uint,
/// Total token supply
pub total_tokens: Uint,
/// Total native token supply
pub total_native_tokens: Uint,
/// PD target locked ratio
Expand All @@ -47,12 +41,13 @@ pub struct RewardsController {
pub epochs_per_year: u64,
}

impl RewardsController {
/// Calculate a new rewards rate
pub fn run(self) -> ValsToUpdate {
impl PosRewardsController {
/// Calculate a new inflation rate for the Proof-of-stake rewards system.
/// Uses the ratios of locked (staked) tokens to the total native token
/// supply to determine the new inflation amount.
pub fn run(self) -> PosValsToUpdate {
let Self {
locked_tokens,
total_tokens,
total_native_tokens,
locked_ratio_target,
locked_ratio_last,
Expand All @@ -63,31 +58,35 @@ impl RewardsController {
epochs_per_year,
} = self;

// Token amounts must be expressed in terms of the raw amount (namnam)
// Token amounts must be expressed in terms of the raw amount
// to properly run the PD controller
let locked = Dec::try_from(locked_tokens)
.expect("Should not fail to convert Uint to Dec");
let total = Dec::try_from(total_tokens)
.expect("Should not fail to convert Uint to Dec");
let total_native = Dec::try_from(total_native_tokens)
.expect("Should not fail to convert Uint to Dec");
let last_inflation_amount = Dec::try_from(last_inflation_amount)
.expect("Should not fail to convert Uint to Dec");

let epochs_py: Dec = epochs_per_year.into();

let locked_ratio = if total.is_zero() {
// Staked ratio
let locked_ratio = if total_native.is_zero() {
Dec::one()
} else {
locked / total
locked / total_native
};

// Max inflation amount for this epoch
let max_inflation = total_native * max_reward_rate / epochs_py;

// Intermediate values
let p_gain = p_gain_nom * max_inflation;
let d_gain = d_gain_nom * max_inflation;

let error = locked_ratio_target - locked_ratio;
let delta_error = locked_ratio_last - locked_ratio;
let control_val = p_gain * error - d_gain * delta_error;

let last_inflation_amount = Dec::try_from(last_inflation_amount)
.expect("Should not fail to convert Uint to Dec");
// New inflation amount
let new_inflation_amount_raw = last_inflation_amount + control_val;
let new_inflation_amount = if new_inflation_amount_raw.is_negative() {
Uint::zero()
Expand All @@ -96,19 +95,101 @@ impl RewardsController {
.to_uint()
.expect("Should not fail to convert Dec to Uint")
};

let max_inflation = max_inflation
.to_uint()
.expect("Should not fail to convert Dec to Uint");

let inflation = std::cmp::min(new_inflation_amount, max_inflation);
ValsToUpdate {
PosValsToUpdate {
locked_ratio,
inflation,
}
}
}

/// PD controller used to dynamically adjust the rewards rates
#[derive(Debug, Clone)]
pub struct ShieldedRewardsController {
/// Locked token amount in the relevant system
pub locked_tokens: Uint,
/// Total native token supply
pub total_native_tokens: Uint,
/// PD target locked amount
pub locked_tokens_target: Uint,
/// PD last locked amount
pub locked_tokens_last: Uint,
/// Maximum reward rate
pub max_reward_rate: Dec,
/// Last inflation amount
pub last_inflation_amount: Uint,
/// Nominal proportional gain
pub p_gain_nom: Dec,
/// Nominal derivative gain
pub d_gain_nom: Dec,
/// Number of epochs per year
pub epochs_per_year: u64,
}

impl ShieldedRewardsController {
/// Calculate a new inflation rate for the Proof-of-stake rewards system.
/// Uses the ratios of locked (staked) tokens to the total native token
/// supply to determine the new inflation amount.
pub fn run(self) -> ShieldedValsToUpdate {
let Self {
locked_tokens,
total_native_tokens,
locked_tokens_target,
locked_tokens_last,
max_reward_rate,
last_inflation_amount,
p_gain_nom,
d_gain_nom,
epochs_per_year,
} = self;

// Token amounts must be expressed in terms of the raw amount
// to properly run the PD controller
let locked = Dec::try_from(locked_tokens)
.expect("Should not fail to convert Uint to Dec");
let locked_amount_target = Dec::try_from(locked_tokens_target)
.expect("Should not fail to convert Uint to Dec");
let locked_amount_last = Dec::try_from(locked_tokens_last)
.expect("Should not fail to convert Uint to Dec");
let total_native = Dec::try_from(total_native_tokens)
.expect("Should not fail to convert Uint to Dec");
let last_inflation_amount = Dec::try_from(last_inflation_amount)
.expect("Should not fail to convert Uint to Dec");

let epochs_py: Dec = epochs_per_year.into();

// Max inflation amount for this epoch
let max_inflation = total_native * max_reward_rate / epochs_py;

// Intermediate values
let p_gain = p_gain_nom * max_reward_rate / epochs_py;
let d_gain = d_gain_nom * max_reward_rate / epochs_py;
let error = locked_amount_target - locked;
let delta_error = locked_amount_last - locked;
let control_val = p_gain * error - d_gain * delta_error;
Comment on lines +165 to +173
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review these computations, to make sure they don't have values of 0


// New inflation amount
let new_inflation_amount_raw = last_inflation_amount + control_val;
let new_inflation_amount = if new_inflation_amount_raw.is_negative() {
Uint::zero()
} else {
new_inflation_amount_raw
.to_uint()
.expect("Should not fail to convert Dec to Uint")
};
let max_inflation = max_inflation
.to_uint()
.expect("Should not fail to convert Dec to Uint");

let inflation = std::cmp::min(new_inflation_amount, max_inflation);
ShieldedValsToUpdate { inflation }
}
}

#[cfg(test)]
mod test {
use std::str::FromStr;
Expand All @@ -117,9 +198,8 @@ mod test {

#[test]
fn test_inflation_calc_up() {
let mut controller = RewardsController {
let mut controller = PosRewardsController {
locked_tokens: Uint::from(2_000_000_000),
total_tokens: Uint::from(4_000_000_000_u64),
total_native_tokens: Uint::from(4_000_000_000_u64),
locked_ratio_target: Dec::from_str("0.66666666").unwrap(),
locked_ratio_last: Dec::from_str("0.5").unwrap(),
Expand All @@ -131,7 +211,7 @@ mod test {
};
dbg!(&controller);

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_0,
inflation: inflation_0,
} = controller.clone().run();
Expand All @@ -143,10 +223,9 @@ mod test {

controller.locked_ratio_last = locked_ratio_0;
controller.last_inflation_amount = inflation_0;
controller.total_tokens += inflation_0;
controller.locked_tokens += inflation_0;

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_1,
inflation: inflation_1,
} = controller.clone().run();
Expand All @@ -160,10 +239,9 @@ mod test {

controller.locked_ratio_last = locked_ratio_1;
controller.last_inflation_amount = inflation_1;
controller.total_tokens += inflation_1;
controller.locked_tokens += inflation_1;

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_2,
inflation: inflation_2,
} = controller.run();
Expand All @@ -178,9 +256,8 @@ mod test {

#[test]
fn test_inflation_calc_down() {
let mut controller = RewardsController {
let mut controller = PosRewardsController {
locked_tokens: Uint::from(900_000_000),
total_tokens: Uint::from(1_000_000_000),
total_native_tokens: Uint::from(1_000_000_000),
locked_ratio_target: Dec::from_str("0.66666666").unwrap(),
locked_ratio_last: Dec::from_str("0.9").unwrap(),
Expand All @@ -192,7 +269,7 @@ mod test {
};
dbg!(&controller);

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_0,
inflation: inflation_0,
} = controller.clone().run();
Expand All @@ -204,10 +281,9 @@ mod test {

controller.locked_ratio_last = locked_ratio_0;
controller.last_inflation_amount = inflation_0;
controller.total_tokens += inflation_0;
controller.locked_tokens += inflation_0;

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_1,
inflation: inflation_1,
} = controller.clone().run();
Expand All @@ -221,10 +297,9 @@ mod test {

controller.locked_ratio_last = locked_ratio_1;
controller.last_inflation_amount = inflation_1;
controller.total_tokens += inflation_1;
controller.locked_tokens += inflation_1;

let ValsToUpdate {
let PosValsToUpdate {
locked_ratio: locked_ratio_2,
inflation: inflation_2,
} = controller.run();
Expand All @@ -247,11 +322,10 @@ mod test {
// let a = (init_locked_ratio * total_tokens).to_uint().unwrap();
let num_rounds = 100;

let mut controller = RewardsController {
let mut controller = PosRewardsController {
locked_tokens: (init_locked_ratio * total_tokens)
.to_uint()
.unwrap(),
total_tokens: Uint::from(total_tokens),
total_native_tokens: Uint::from(total_tokens),
locked_ratio_target: Dec::from_str("0.66666666").unwrap(),
locked_ratio_last: init_locked_ratio,
Expand All @@ -264,7 +338,7 @@ mod test {
dbg!(&controller);

for round in 0..num_rounds {
let ValsToUpdate {
let PosValsToUpdate {
locked_ratio,
inflation,
} = controller.clone().run();
Expand All @@ -276,7 +350,6 @@ mod test {
{rate}",
);
controller.last_inflation_amount = inflation;
controller.total_tokens += inflation;
controller.total_native_tokens += inflation;

// if rate.abs_diff(&controller.max_reward_rate)
Expand All @@ -285,11 +358,12 @@ mod test {
// controller.locked_tokens = controller.total_tokens;
// }

let tot_tokens = u64::try_from(controller.total_tokens).unwrap();
let tot_tokens =
u64::try_from(controller.total_native_tokens).unwrap();
let change_staked_tokens =
(staking_growth * tot_tokens).to_uint().unwrap();
controller.locked_tokens = std::cmp::min(
controller.total_tokens,
controller.total_native_tokens,
controller.locked_tokens + change_staked_tokens,
);

Expand Down
7 changes: 4 additions & 3 deletions crates/core/src/types/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,8 +1009,9 @@ pub struct MaspParams {
pub kd_gain_nom: Dec,
/// Shielded Pool nominal proportional gain for the given token
pub kp_gain_nom: Dec,
/// Locked ratio for the given token
pub locked_ratio_target: Dec,
/// Target amount for the given token that is locked in the shielded pool
/// TODO: should this be a Uint or DenominatedAmount???
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a uint, I think. Whatever the max supply of a token is (probably u256?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah but we have the weird MASP denom splitting thing, better think carefully about this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uint (or Amount in the worst case) should be fine here. The inflation computations are done at a high level, and the low-level MASP denom splitting is taken care of by the MASP conversion logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think Uint is best, as it is clearly readable and digestible by humans, whereas Amount will not be in this scenario.

pub locked_amount_target: u64,
}

impl Default for MaspParams {
Expand All @@ -1019,7 +1020,7 @@ impl Default for MaspParams {
max_reward_rate: Dec::from_str("0.1").unwrap(),
kp_gain_nom: Dec::from_str("0.25").unwrap(),
kd_gain_nom: Dec::from_str("0.25").unwrap(),
locked_ratio_target: Dec::from_str("0.6667").unwrap(),
locked_amount_target: 10_000_u64,
}
}
}
Expand Down
Loading
Loading