Skip to content

Commit

Permalink
Integrated PD controller support.
Browse files Browse the repository at this point in the history
  • Loading branch information
murisi committed Oct 16, 2023
1 parent 79af00e commit 0380ea2
Show file tree
Hide file tree
Showing 12 changed files with 547 additions and 211 deletions.
14 changes: 14 additions & 0 deletions apps/src/lib/config/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ pub mod genesis_config {
pub vp: Option<String>,
// Initial balances held by accounts defined elsewhere.
pub balances: Option<HashMap<String, token::Amount>>,
// Token parameters
pub parameters: Option<token::Parameters>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -404,6 +406,9 @@ pub mod genesis_config {
implicit_accounts: &HashMap<String, ImplicitAccount>,
) -> TokenAccount {
TokenAccount {
last_locked_ratio: Dec::zero(),
last_inflation: token::Amount::zero(),
parameters: config.parameters.as_ref().unwrap().to_owned(),
address: Address::decode(config.address.as_ref().unwrap()).unwrap(),
denom: config.denom,
balances: config
Expand Down Expand Up @@ -818,6 +823,12 @@ pub struct TokenAccount {
/// Accounts' balances of this token
#[derivative(PartialOrd = "ignore", Ord = "ignore")]
pub balances: HashMap<Address, token::Amount>,
/// Token parameters
pub parameters: token::Parameters,
/// Token inflation from the last epoch (read + write for every epoch)
pub last_inflation: token::Amount,
/// Token shielded ratio from the last epoch (read + write for every epoch)
pub last_locked_ratio: Dec,
}

#[derive(
Expand Down Expand Up @@ -1100,6 +1111,9 @@ pub fn genesis(num_validators: u64) -> Genesis {
.into_iter()
.map(|(k, v)| (k, token::Amount::from_uint(v, denom).unwrap()))
.collect(),
parameters: token::Parameters::default(),
last_inflation: token::Amount::zero(),
last_locked_ratio: Dec::zero(),
})
.collect();
Genesis {
Expand Down
3 changes: 2 additions & 1 deletion apps/src/lib/node/ledger/shell/finalize_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use namada::ledger::pos::{namada_proof_of_stake, staking_token_address};
use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY;
use namada::ledger::storage_api::token::credit_tokens;
use namada::ledger::storage_api::{pgf, StorageRead, StorageWrite};
use namada::ledger::{inflation, protocol, replay_protection};
use namada::ledger::{protocol, replay_protection};
use namada::core::ledger::inflation;
use namada::proof_of_stake::{
delegator_rewards_products_handle, find_validator_by_raw_hash,
read_last_block_proposer_address, read_pos_params, read_total_stake,
Expand Down
21 changes: 21 additions & 0 deletions apps/src/lib/node/ledger/shell/init_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,14 +330,35 @@ where
address,
denom,
balances,
parameters,
last_inflation,
last_locked_ratio,
} in accounts
{
// Init token parameters and last inflation and caching rates
parameters.init_storage(&address, &mut self.wl_storage);
self.wl_storage
.write(&token::masp_last_inflation(&address), last_inflation)
.unwrap();
self.wl_storage
.write(&token::masp_last_locked_ratio(&address), last_locked_ratio)
.unwrap();
// associate a token with its denomination.
write_denom(&mut self.wl_storage, &address, denom).unwrap();

let mut total_balance_for_token = token::Amount::default();
for (owner, amount) in balances {
total_balance_for_token += amount;
credit_tokens(&mut self.wl_storage, &address, &owner, amount)
.unwrap();
}
// Write the total amount of tokens for the ratio
self.wl_storage
.write(
&token::minted_balance_key(&address),
total_balance_for_token,
)
.unwrap();
}
}

Expand Down
2 changes: 2 additions & 0 deletions apps/src/lib/node/ledger/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1980,6 +1980,7 @@ mod test_utils {

/// We test that on shell shutdown, the tx queue gets persisted in a DB, and
/// on startup it is read successfully
#[cfg(feature = "testing")]
#[test]
fn test_tx_queue_persistence() {
let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap();
Expand Down Expand Up @@ -2017,6 +2018,7 @@ mod test_utils {
.storage
.begin_block(BlockHash::default(), BlockHeight(1))
.expect("begin_block failed");
token::testing::init_token_storage(&mut shell.wl_storage, 60);
let keypair = gen_keypair();
// enqueue a wrapper tx
let mut wrapper =
Expand Down
2 changes: 2 additions & 0 deletions apps/src/lib/node/ledger/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ mod tests {
assert_eq!(result, None);
}

#[cfg(feature = "testing")]
#[test]
fn test_commit_block() {
let db_path =
Expand All @@ -144,6 +145,7 @@ mod tests {
storage.block.pred_epochs.new_epoch(BlockHeight(100));
// make wl_storage to update conversion for a new epoch
let mut wl_storage = WlStorage::new(WriteLog::default(), storage);
namada::types::token::testing::init_token_storage(&mut wl_storage, 60);
update_allowed_conversions(&mut wl_storage)
.expect("update conversions failed");
wl_storage.commit_block().expect("commit failed");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
//! proof-of-stake, providing liquity to shielded asset pools, and public goods
//! funding.
use namada_core::types::dec::Dec;

use crate::types::dec::Dec;
use crate::types::token;

/// The domains of inflation
Expand Down Expand Up @@ -114,7 +113,7 @@ impl RewardsController {
mod test {
use std::str::FromStr;

use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES;
use crate::types::token::NATIVE_MAX_DECIMAL_PLACES;

use super::*;

Expand Down
1 change: 1 addition & 0 deletions core/src/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod gas;
pub mod governance;
#[cfg(any(feature = "abciplus", feature = "abcipp"))]
pub mod ibc;
pub mod inflation;
pub mod parameters;
pub mod pgf;
pub mod replay_protection;
Expand Down
171 changes: 160 additions & 11 deletions core/src/ledger/storage/masp_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ use masp_primitives::sapling::Node;
use crate::types::address::Address;
use crate::types::storage::Epoch;
use crate::types::token::MaspDenom;
use crate::types::dec::Dec;
use crate::ledger::storage_api::{ResultExt, StorageRead, StorageWrite};
use crate::types::{address, token};
use crate::ledger::parameters;
use crate::ledger::inflation::{RewardsController, ValsToUpdate};

/// Inflation is implicitly denominated by this value. The lower this figure,
/// the less precise inflation computations are. The higher this figure, the
/// larger the fixed-width types that are required to carry out inflation
/// computations. This value should be fixed constant for each asset type - here
/// we have simplified it and made it constant across asset types.
const PRECISION: u64 = 100;

/// A representation of the conversion state
#[derive(Debug, Default, BorshSerialize, BorshDeserialize)]
Expand All @@ -27,6 +39,139 @@ pub struct ConversionState {
>,
}

fn calculate_masp_rewards<D, H>(
wl_storage: &mut super::WlStorage<D, H>,
addr: &Address,
) -> crate::ledger::storage_api::Result<(u32, u32)>
where
D: 'static + super::DB + for<'iter> super::DBIter<'iter>,
H: 'static + super::StorageHasher,
{
let masp_addr = address::masp();
// Query the storage for information

//// information about the amount of tokens on the chain
let total_tokens: token::Amount =
wl_storage.read(&token::minted_balance_key(&address::nam()))?
.expect("the total supply key should be here");

// total staked amount in the Shielded pool
let total_token_in_masp: token::Amount = wl_storage
.read(&token::balance_key(addr, &masp_addr))?
.unwrap_or_default();

let epochs_per_year: u64 = wl_storage
.read(&parameters::storage::get_epochs_per_year_key())?
.expect("");

//// Values from the last epoch
let last_inflation: token::Amount = wl_storage
.read(&token::masp_last_inflation(addr))
.expect("failure to read last inflation")
.expect("");

let last_locked_ratio: Dec = wl_storage
.read(&token::masp_last_locked_ratio(addr))
.expect("failure to read last inflation")
.expect("");

//// Parameters for each token
let max_reward_rate: Dec = wl_storage
.read(&token::masp_max_reward_rate(addr))
.expect("max reward should properly decode")
.expect("");

let kp_gain_nom: Dec = wl_storage
.read(&token::masp_kp_gain(addr))
.expect("kp_gain_nom reward should properly decode")
.expect("");

let kd_gain_nom: Dec = wl_storage
.read(&token::masp_kd_gain(addr))
.expect("kd_gain_nom reward should properly decode")
.expect("");

let locked_target_ratio: Dec = wl_storage
.read(&token::masp_locked_ratio_target(addr))?
.expect("");

// Creating the PD controller for handing out tokens
let controller = RewardsController {
locked_tokens: total_token_in_masp,
total_tokens,
locked_ratio_target: locked_target_ratio,
locked_ratio_last: last_locked_ratio,
max_reward_rate,
last_inflation_amount: last_inflation,
p_gain_nom: kp_gain_nom,
d_gain_nom: kd_gain_nom,
epochs_per_year,
};

let ValsToUpdate {
locked_ratio,
inflation,
} = RewardsController::run(controller);

// inflation-per-token = inflation / locked tokens = n/PRECISION
// ∴ n = (inflation * PRECISION) / locked tokens
// Since we must put the notes in a compatible format with the
// note format, we must make the inflation amount discrete.
let noterized_inflation = if total_token_in_masp.is_zero() {
0u32
} else {
crate::types::uint::Uint::try_into(
(inflation.raw_amount() * PRECISION) / total_token_in_masp.raw_amount()
).unwrap()
};

tracing::debug!(
"Controller, call: total_in_masp {:?}, total_tokens {:?}, \
locked_target_ratio {:?}, last_locked_ratio {:?}, max_reward_rate \
{:?}, last_inflation {:?}, kp_gain_nom {:?}, kd_gain_nom {:?}, \
epochs_per_year {:?}",
total_token_in_masp,
total_tokens,
locked_target_ratio,
last_locked_ratio,
max_reward_rate,
last_inflation,
kp_gain_nom,
kd_gain_nom,
epochs_per_year,
);
tracing::debug!("Please give me: {:?}", addr);
tracing::debug!("Ratio {:?}", locked_ratio);
tracing::debug!("inflation from the pd controller {:?}", inflation);
tracing::debug!("total in the masp {:?}", total_token_in_masp);
tracing::debug!("Please give me inflation: {:?}", noterized_inflation);

// Is it fine to write the inflation rate, this is accurate,
// but we should make sure the return value's ratio matches
// this new inflation rate in 'update_allowed_conversions',
// otherwise we will have an inaccurate view of inflation
wl_storage
.write(
&token::masp_last_inflation(addr),
(total_token_in_masp / PRECISION) * u64::from(noterized_inflation),
)
.expect("unable to encode new inflation rate (Decimal)");

wl_storage
.write(&token::masp_last_locked_ratio(addr), locked_ratio)
.expect("unable to encode new locked ratio (Decimal)");

// to make it conform with the expected output, we need to
// move it to a ratio of x/100 to match the masp_rewards
// function This may be unneeded, as we could describe it as a
// ratio of x/1

Ok((
noterized_inflation,
PRECISION.try_into().expect("inflation precision too large"),
))
}

// This is only enabled when "wasm-runtime" is on, because we're using rayon
#[cfg(feature = "wasm-runtime")]
/// Update the MASP's allowed conversions
Expand All @@ -46,22 +191,20 @@ where
};
use rayon::prelude::ParallelSlice;

use crate::ledger::storage_api::{ResultExt, StorageRead, StorageWrite};
use crate::types::storage::{self, KeySeg};
use crate::types::{address, token};

// The derived conversions will be placed in MASP address space
let masp_addr = address::masp();
let key_prefix: storage::Key = masp_addr.to_db_key().into();

let masp_rewards = address::masp_rewards();
let mut masp_reward_keys: Vec<_> = masp_rewards.keys().collect();
let tokens = address::tokens();
let mut masp_reward_keys: Vec<_> = tokens.into_keys().collect();
// Put the native rewards first because other inflation computations depend
// on it
masp_reward_keys.sort_unstable_by(|x, y| {
if (**x == address::nam()) == (**y == address::nam()) {
if (*x == address::nam()) == (*y == address::nam()) {
Ordering::Equal
} else if **x == address::nam() {
} else if *x == address::nam() {
Ordering::Less
} else {
Ordering::Greater
Expand All @@ -83,9 +226,17 @@ where
// Conversions from the previous to current asset for each address
let mut current_convs =
BTreeMap::<(Address, MaspDenom), AllowedConversion>::new();
// Native token inflation values are always with respect to this
let mut ref_inflation = 0;
// Reward all tokens according to above reward rates
for addr in masp_reward_keys {
let reward = masp_rewards[addr];
for addr in &masp_reward_keys {
let reward = calculate_masp_rewards(wl_storage, addr)
.expect("Calculating the masp rewards should not fail");
if *addr == address::nam() {
// The reference inflation is the denominator of the native token
// inflation, which is always a constant
ref_inflation = reward.1;
}
// Dispense a transparent reward in parallel to the shielded rewards
let addr_bal: token::Amount = wl_storage
.read(&token::balance_key(addr, &masp_addr))?
Expand All @@ -104,8 +255,6 @@ where
denom,
wl_storage.storage.block.epoch,
);
// Native token inflation values are always with respect to this
let ref_inflation = masp_rewards[&address::nam()].1;
// Get the last rewarded amount of the native token
let normed_inflation = wl_storage
.storage
Expand Down Expand Up @@ -249,7 +398,7 @@ where

// Add purely decoding entries to the assets map. These will be
// overwritten before the creation of the next commitment tree
for addr in masp_rewards.keys() {
for addr in masp_reward_keys {
for denom in token::MaspDenom::iter() {
// Add the decoding entry for the new asset type. An uncommited
// node position is used since this is not a conversion.
Expand Down
Loading

0 comments on commit 0380ea2

Please sign in to comment.