diff --git a/core/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs index b28e1b9eb85..1eb3592685e 100644 --- a/core/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -874,4 +874,4 @@ mod test { ); assert!(basetree_verification_res); } -} \ No newline at end of file +} diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 9e813490492..b68eb1e5573 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -10,9 +10,9 @@ use arse_merkle_tree::traits::Value; use arse_merkle_tree::{InternalKey, Key as TreeKey}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; +use regex::Regex; use serde::{Deserialize, Serialize}; use thiserror::Error; -use regex::Regex; use crate::bytes::ByteBuf; use crate::types::address::{self, Address}; @@ -772,24 +772,36 @@ impl_int_key_seg!(u32, i32, 4); impl_int_key_seg!(u64, i64, 8); impl_int_key_seg!(u128, i128, 16); -impl KeySeg for (Epoch, Epoch) { +impl KeySeg for (Epoch, Option) { fn parse(string: String) -> Result where Self: Sized, { - let re = Regex::new(r"\((\d{1}),(\d{1})\)").unwrap(); + let re = Regex::new(r"\((\d{1}),(-?)(\d?)\)").unwrap(); let caps = re.captures(string.as_str()).unwrap(); let first = caps.get(1).map(|m| m.as_str().to_owned()).unwrap(); - let second = caps.get(2).map(|m| m.as_str().to_owned()).unwrap(); - - let first = u64::parse(first)?; - let second = u64::parse(second)?; + let second = caps.get(2).map_or(None, |m| Some(m.as_str().to_owned())); + let third = caps.get(3).map_or(None, |m| Some(m.as_str().to_owned())); - Ok((Epoch(first), Epoch(second))) + let first = u64::parse(first)?; + match second { + Some(epoch_str) => { + let third = u64::parse(epoch_str)?; + Ok((Epoch(first), Some(Epoch(third)))) + } + None => Ok((Epoch(first), None)), + } } fn raw(&self) -> String { - format!("({},{})",self.0,self.1) + match self.1 { + Some(epoch) => { + format!("({},{})", self.0, epoch) + } + None => { + format!("({},-)", self.0) + } + } } fn to_db_key(&self) -> DbKeySeg { diff --git a/proof_of_stake/src/epoched_new.rs b/proof_of_stake/src/epoched_new.rs index 872fa376ccd..b173dcfc70f 100644 --- a/proof_of_stake/src/epoched_new.rs +++ b/proof_of_stake/src/epoched_new.rs @@ -219,7 +219,11 @@ impl EpochedDelta where FutureEpochs: EpochOffset, - Data: BorshSerialize + BorshDeserialize + ops::Add + 'static + Debug, + Data: BorshSerialize + + BorshDeserialize + + ops::Add + + 'static + + Debug, { /// Open the handle pub fn open(key: storage::Key) -> Self { @@ -315,7 +319,8 @@ where None => return Ok(None), Some(last_update) => { let data_handler = self.get_data_handler(); - let future_most_epoch = last_update + FutureEpochs::value(params); + let future_most_epoch = + last_update + FutureEpochs::value(params); // Epoch can be a lot greater than the epoch where // a value is recorded, we check the upper bound // epoch of the LazyMap data @@ -325,7 +330,7 @@ where match (&mut sum, next) { (Some(_), Ok((next_epoch, next_val))) => { if next_epoch > epoch { - return Ok(sum) + return Ok(sum); } else { sum = sum.map(|cur_sum| cur_sum + next_val) } @@ -337,9 +342,7 @@ where sum = Some(next_val) } } - (Some(_), Err(_)) => { - return Ok(sum) - } + (Some(_), Err(_)) => return Ok(sum), // perhaps elaborate with an error _ => return Ok(None), }; @@ -383,9 +386,10 @@ where /// TODO: maybe better description /// Update the data associated with epochs, if needed. Any key-value with - /// epoch before the oldest stored epoch is added to the key-value with the oldest stored epoch that is kept. If the oldest - /// stored epoch is not already associated with some value, the latest - /// value from the dropped values, if any, is associated with it. + /// epoch before the oldest stored epoch is added to the key-value with the + /// oldest stored epoch that is kept. If the oldest stored epoch is not + /// already associated with some value, the latest value from the + /// dropped values, if any, is associated with it. fn update_data( &self, storage: &mut S, @@ -404,18 +408,25 @@ where let data_handler = self.get_data_handler(); let mut new_oldest_value: Option = None; for offset in 1..diff + 1 { - let old = data_handler - .remove(storage, &Epoch(expected_oldest_epoch.0 - offset))?; + let old = data_handler.remove( + storage, + &Epoch(expected_oldest_epoch.0 - offset), + )?; if old.is_some() { match new_oldest_value { - Some(latest) => new_oldest_value = Some(latest + old.unwrap()), + Some(latest) => { + new_oldest_value = Some(latest + old.unwrap()) + } None => new_oldest_value = old, } } } if let Some(new_oldest_value) = new_oldest_value { // TODO we can add `contains_key` to LazyMap - if data_handler.get(storage, &expected_oldest_epoch)?.is_none() { + if data_handler + .get(storage, &expected_oldest_epoch)? + .is_none() + { data_handler.insert( storage, expected_oldest_epoch, diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 7fe319b5b07..bf71968c016 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -28,20 +28,23 @@ use std::num::TryFromIntError; use epoched::{ DynEpochOffset, EpochOffset, Epoched, EpochedDelta, OffsetPipelineLen, }; -use namada_core::ledger::storage_api::{self, StorageRead, StorageWrite}; use namada_core::ledger::storage_api::collections::{LazyCollection, LazyMap}; +use namada_core::ledger::storage_api::{self, StorageRead, StorageWrite}; use namada_core::types::address::{self, Address, InternalAddress}; -use namada_core::types::{key::common, token, storage::Epoch}; +use namada_core::types::key::common; +use namada_core::types::storage::Epoch; +use namada_core::types::token; use parameters::PosParams; use rust_decimal::Decimal; +use storage::{validator_max_commission_rate_change_key, validator_state_key}; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, GenesisValidator, - Slash, SlashType, Slashes, TotalDeltas, Unbond, Unbonds, - ValidatorConsensusKeys, ValidatorConsensusKeys_NEW, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorDeltas, ValidatorStates_NEW, - ValidatorDeltas_NEW, ValidatorSets_NEW + ActiveValidator, Bond_NEW, Bonds, Bonds_NEW, CommissionRates, + CommissionRates_NEW, GenesisValidator, Slash, SlashType, Slashes, + TotalDeltas, TotalDeltas_NEW, Unbond, Unbonds, ValidatorConsensusKeys, + ValidatorConsensusKeys_NEW, ValidatorDeltas, ValidatorDeltas_NEW, + ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorSets_NEW, + ValidatorState, ValidatorStates, ValidatorStates_NEW, }; use crate::btree_set::BTreeSetShims; @@ -1384,6 +1387,110 @@ fn bond_tokens( }) } +/// Bond tokens to a validator (self-bond or delegation). +#[allow(clippy::too_many_arguments)] +fn bond_tokens( + params: &PosParams, + validator_state: ValidatorStates_NEW, + bond_id: &BondId, + current_bond: Bonds_NEW, + amount: token::Amount, + validator_deltas: ValidatorDeltas_NEW, + total_deltas: &mut TotalDeltas_NEW, + validator_set: &mut ValidatorSets_NEW, + current_epoch: Epoch, +) -> Result { + if amount == token::Amount::default() { + return Err(BondError::ZeroAmount); + } + // Do we have error handling to make sure the storage key exists? Since + // these input args are no lenger Options I.E. want to make sure this is + // a real validator + + // Check that it's not inactive anywhere from the current epoch + // to the pipeline offset + for epoch in current_epoch.iter_range(OffsetPipelineLen::value(params)) { + if let Some(ValidatorState::Inactive) = validator_state.get(epoch) { + return Err(BondError::InactiveValidator( + bond_id.validator.clone(), + )); + } + } + + // Check the validator state + match validator_state { + None => { + return Err(BondError::NotAValidator(bond_id.validator.clone())); + } + Some(validator_state) => {} + } + + // Update or create the bond + // + let mut value = Bond { + pos_deltas: HashMap::default(), + neg_deltas: token::Amount::default(), + }; + // Initialize the bond at the pipeline offset + let update_offset = DynEpochOffset::PipelineLen; + value + .pos_deltas + .insert(current_epoch + update_offset.value(params), amount); + let bond = match current_bond { + None => EpochedDelta::init_at_offset( + value, + current_epoch, + update_offset, + params, + ), + Some(mut bond) => { + bond.add_at_offset(value, current_epoch, update_offset, params); + bond + } + }; + + // Update validator set. This has to be done before we update the + // `validator_deltas`, because we need to look-up the validator with + // its voting power before the change. + let token_change = token::Change::from(amount); + update_validator_set( + params, + &bond_id.validator, + token_change, + update_offset, + validator_set, + validator_deltas.as_ref(), + current_epoch, + ); + + // Update validator's total deltas and total staked token deltas + let delta = token::Change::from(amount); + let validator_deltas = match validator_deltas { + Some(mut validator_deltas) => { + validator_deltas.add_at_offset( + delta, + current_epoch, + update_offset, + params, + ); + validator_deltas + } + None => EpochedDelta::init_at_offset( + delta, + current_epoch, + update_offset, + params, + ), + }; + + total_deltas.add_at_offset(delta, current_epoch, update_offset, params); + + Ok(BondData { + bond, + validator_deltas, + }) +} + struct UnbondData { pub unbond: Unbonds, } @@ -1687,34 +1794,52 @@ pub fn validator_sets_handle() -> ValidatorSets_NEW { pub fn validator_consensus_key_handle( validator: &Address, ) -> ValidatorConsensusKeys_NEW { - let key = storage::validator_consensus_key_key(&validator); + let key = storage::validator_consensus_key_key(validator); crate::epoched_new::Epoched::open(key) } /// Get the storage handle to a PoS validator's state -pub fn validator_state_handle( - validator: &Address -) -> ValidatorStates_NEW { - let key = storage::validator_state_key(&validator); +pub fn validator_state_handle(validator: &Address) -> ValidatorStates_NEW { + let key = storage::validator_state_key(validator); crate::epoched_new::Epoched::open(key) } /// Get the storage handle to a PoS validator's deltas -pub fn validator_deltas_handle( - validator: &Address -) -> ValidatorDeltas_NEW { - let key = storage::validator_deltas_key(&validator); +pub fn validator_deltas_handle(validator: &Address) -> ValidatorDeltas_NEW { + let key = storage::validator_deltas_key(validator); crate::epoched_new::EpochedDelta::open(key) } +/// Get the storage handle to the total deltas +pub fn total_deltas_handle() -> TotalDeltas_NEW { + let key = storage::total_deltas_key(); + crate::epoched_new::EpochedDelta::open(key) +} + +/// Get the storage handle to a PoS validator's commission rate +pub fn validator_commission_rate_handle( + validator: &Address, +) -> CommissionRates_NEW { + let key = storage::validator_commission_rate_key(validator); + crate::epoched_new::Epoched::open(key) +} + /// Get the storage handle to a bonds pub fn bond_handle( source: &Address, - validator: &Address -) -> LazyMap { - let bond_id = BondId {source: source.clone(), validator: validator.clone()}; - let key = storage::bond_key(&bond_id); - LazyMap::open(key) + validator: &Address, + get_remaining: bool, +) -> Bonds_NEW { + let bond_id = BondId { + source: source.clone(), + validator: validator.clone(), + }; + let key = if get_remaining { + storage::bond_remaining_key(&bond_id) + } else { + storage::bond_amount_key(&bond_id) + }; + crate::epoched_new::EpochedDelta::open(key) } /// new init genesis @@ -1725,17 +1850,26 @@ pub fn init_genesis_NEW( current_epoch: namada_core::types::storage::Epoch, ) -> storage_api::Result<()> where - S: for<'iter> StorageRead<'iter> + StorageWrite, + S: for<'iter> StorageRead<'iter> + StorageWrite + PosBase, { - // validator_sets_handle().init_at_genesis(storage, value, current_epoch) + let mut active: BTreeSet = BTreeSet::default(); + let mut total_bonded = token::Amount::default(); + for GenesisValidator { address, tokens, consensus_key, commission_rate, - max_commission_rate_change + max_commission_rate_change, } in validators { + storage.write_validator_address_raw_hash(&address, &consensus_key); + storage.write_validator_max_commission_rate_change( + &address, + &max_commission_rate_change, + ); + total_bonded += tokens; + validator_consensus_key_handle(&address).init_at_genesis( storage, consensus_key, @@ -1744,15 +1878,64 @@ where validator_state_handle(&address).init_at_genesis( storage, ValidatorState::Candidate, - current_epoch + current_epoch, )?; let delta = token::Change::from(tokens); validator_deltas_handle(&address).init_at_genesis( storage, delta, - current_epoch + current_epoch, )?; + // Do we want source to be address or None? + bond_handle(&address, &address, false).init_at_genesis( + storage, + delta, + current_epoch, + )?; + bond_handle(&address, &address, true).init_at_genesis( + storage, + delta, + current_epoch, + )?; + validator_commission_rate_handle(&address).init_at_genesis( + storage, + commission_rate, + current_epoch, + )?; + + active.insert(WeightedValidator { + bonded_stake: tokens.into(), + address: address.clone(), + }); } + // Pop the smallest validators from the active set until its size is under + // the limit and insert them into the inactive set + let mut inactive: BTreeSet = BTreeSet::default(); + while active.len() > params.max_validator_slots as usize { + match active.pop_first_shim() { + Some(first) => { + inactive.insert(first); + } + None => break, + } + } + let validator_set = ValidatorSet { active, inactive }; + validator_sets_handle().init_at_genesis( + storage, + validator_set, + current_epoch, + )?; + + total_deltas_handle().init_at_genesis( + storage, + token::Change::from(total_bonded), + current_epoch, + ); + storage.credit_tokens( + &storage.staking_token_address(), + &PosBase::POS_ADDRESS, + total_bonded, + ); Ok(()) } @@ -1817,7 +2000,7 @@ where /// Write PoS validator's consensus key (used for signing block votes). /// Note: for EpochedDelta, write the value to change storage by -pub fn write_validator_deltas( +pub fn update_validator_deltas( storage: &mut S, params: &PosParams, validator: &Address, @@ -1829,11 +2012,10 @@ where { let handle = validator_deltas_handle(&validator); let offset = OffsetPipelineLen::value(params); - - // TODO: either use read_validator_deltas here to update the value properly - // or use a new or updated method to update the Data val for EpochedDelta (set currently just sets the val like discrete Epoched) - handle.set(storage, delta, current_epoch, offset) + let val = handle.get_delta_val(storage, current_epoch, params)?.unwrap_or_default(); + handle.set(storage, val + delta, current_epoch, offset) } + /// Read PoS validator's state. pub fn read_validator_state( storage: &S, @@ -1870,28 +2052,223 @@ pub fn read_bonds( params: &PosParams, source: &Address, validator: &Address, - epoch: namada_core::types::storage::Epoch, -) -> storage_api::Result>> + // epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> where S: for<'iter> StorageRead<'iter>, { - // What exactly should we have this read? Should we look up by source, validator? - // Should it be the sum of all bonds? - todo!() + // What do we actually want to be read here? Do we want epoch to be an input + // arg? This seems like a fine option for now. + // Could build and return a LazyMap for all the bonds that + // exist at the input epoch, where `Epoch` inside LazyMap would be the + // ending epoch of the bond + Ok(Some(bond_handle(source, validator))) } /// Write PoS validator's consensus key (used for signing block votes). pub fn write_bond( storage: &mut S, params: &PosParams, + source: &Address, validator: &Address, - state: ValidatorState, + amount: token::Amount, current_epoch: namada_core::types::storage::Epoch, ) -> storage_api::Result<()> where S: for<'iter> StorageRead<'iter> + StorageWrite, { - let handle = validator_state_handle(&validator); + let handle = bond_handle(source, validator); let offset = OffsetPipelineLen::value(params); - handle.set(storage, state, current_epoch, offset) + let map_key: (Epoch, Option) = (current_epoch + offset, None); + // Do some better error handling here + match handle.insert(storage, map_key, amount)? { + Some(_) => Ok(()), + None => Ok(()), + } +} + +/// Read PoS validator's commission rate. +pub fn read_validator_commission_rate( + storage: &S, + params: &PosParams, + validator: &Address, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: for<'iter> StorageRead<'iter>, +{ + let handle = validator_commission_rate_handle(validator); + handle.get(storage, epoch, params) +} + +/// Write PoS validator's commission rate. +pub fn write_validator_commission_rate( + storage: &mut S, + params: &PosParams, + validator: &Address, + rate: Decimal, + current_epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result<()> +where + S: for<'iter> StorageRead<'iter> + StorageWrite, +{ + let handle = validator_commission_rate_handle(&validator); + let offset = OffsetPipelineLen::value(params); + handle.set(storage, rate, current_epoch, offset) + + // Do I want to do any checking for valid rate changes here (prob not)? +} + +/// Read PoS validator's max commission rate change. +pub fn read_validator_commission_rate( + storage: &S, + validator: &Address, +) -> storage_api::Result> +where + S: for<'iter> StorageRead<'iter>, +{ + let key = validator_max_commission_rate_change_key(validator); + storage.read(&key) +} + +pub fn write_validator_max_commission_rate_change( + storage: &S, + validator: &Address, + change: Decimal, +) -> storage_api::Result<()> +where + S: for<'iter> StorageRead<'iter> + StorageWrite, +{ + let key = validator_max_commission_rate_change_key(validator); + storage.write(&key, change) +} + +/// Read PoS total stake (sum of deltas). +pub fn read_total_stake( + storage: &S, + params: &PosParams, + validator: &Address, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: for<'iter> StorageRead<'iter>, +{ + let handle = total_deltas_handle(); + handle.get_sum(storage, epoch, params) +} + +/// Write PoS total deltas. +/// Note: for EpochedDelta, write the value to change storage by +pub fn update_total_deltas( + storage: &mut S, + params: &PosParams, + delta: token::Change, + current_epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result<()> +where + S: for<'iter> StorageRead<'iter> + StorageWrite, +{ + let handle = total_deltas_handle(); + let offset = OffsetPipelineLen::value(params); + let val = handle + .get_delta_val(storage, current_epoch, params)? + .unwrap(); + handle.set(storage, val + delta, current_epoch, offset) +} + +/// NEW: Self-bond tokens to a validator when `source` is `None` or equal to +/// the `validator` address, or delegate tokens from the `source` to the +/// `validator`. +fn bond_tokens_new( + storage: &mut S, + source: Option<&Address>, + validator: &Address, + amount: token::Amount, + current_epoch: Epoch, +) -> storage_api::Result<()> +where + S: for<'iter> StorageRead<'iter> + StorageWrite + PosReadOnly, +{ + if let Some(source) = source { + if source != validator && self.is_validator(source)? { + return Err( + BondError::SourceMustNotBeAValidator(source.clone()).into() + ); + } + } + if !storage.has_key(&validator_state_key(validator)) { + return Err(BondError::NotAValidator(address)).into(); + } + + let params = storage.read_pos_params()?; + let validator_state_handle = validator_state_handle(validator); + let source = source.unwrap_or(validator); + let bond_amount_handle = bond_handle(source, validator, false); + let bond_remain_handle = bond_handle(source, validator, true); + let validator_deltas_handle = validator_deltas_handle(validator); + let total_deltas_handle = total_deltas_handle(); + let validator_set_handle = validator_sets_handle(); + + // Check that validator is not inactive at anywhere between the current + // epoch and pipeline offset + for epoch in current_epoch.iter_range(params.pipeline_len) { + if let Some(ValidatorState::Inactive) = + validator_state_handle.get(storage, epoch, ¶ms) + { + return Err(BondError::InactiveValidator(validator)); + } + } + + // Initialize or update the bond at the pipeline offset + let bond_id = BondId { source, validator }; + if storage.has_key(&storage::bond_key(&bond_id))? { + let cur_amount = bond_amount_handle + .get_delta_val(storage, current_epoch, ¶ms)? + .unwrap_or_default(); + let cur_remain = bond_remain_handle + .get_delta_val(storage, current_epoch, ¶ms)? + .unwrap_or_default(); + bond_amount_handle.set( + storage, + cur_amount + amount, + current_epoch, + offset, + )?; + bond_remain_handle.set( + storage, + cur_remain + amount, + current_epoch, + offset, + )?; + } else { + bond_amount_handle.init( + storage, + amount, + current_epoch, + params.pipeline_len, + )?; + bond_remain_handle.init( + storage, + amount, + current_epoch, + params.pipeline_len, + )?; + } + + // Update the validator set + // TODO: are we going to store a BTreeSet or have some lazy stuff for this too? + + + // Update the validator and total deltas + update_validator_deltas(storage, ¶ms, validator, delta, current_epoch)?; + update_total_deltas(storage, ¶ms, amount, current_epoch)?; + + // Transfer the bonded tokens from the source to PoS + self.transfer( + &self.staking_token_address(), + amount, + source, + &PosReadOnly::POS_ADDRESS, + )?; + Ok(()) } diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index fafb69c70b7..cada11d5cbd 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -2,7 +2,6 @@ use namada_core::ledger::storage::types::{decode, encode}; use namada_core::ledger::storage::{self, Storage, StorageHasher}; - use namada_core::types::address::Address; use namada_core::types::storage::{DbKeySeg, Key, KeySeg}; use namada_core::types::{key, token}; @@ -24,6 +23,8 @@ const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = "max_commission_rate_change_NEW"; const SLASHES_PREFIX: &str = "slash_NEW"; const BOND_STORAGE_KEY: &str = "bond_NEW"; +const BOND_AMOUNT_STORAGE_KEY: &str = "bond_amount"; +const BOND_REMAINING_STORAGE_KEY: &str = "bond_remaining"; const UNBOND_STORAGE_KEY: &str = "unbond_NEW"; const VALIDATOR_SET_STORAGE_KEY: &str = "validator_set_NEW"; const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas_NEW"; @@ -263,6 +264,22 @@ pub fn bond_key(bond_id: &BondId) -> Key { .expect("Cannot obtain a storage key") } +/// Storage key for the total undeducted amount in the bond with a given ID +/// (source and validator) +pub fn bond_amount_key(bond_id: &BondId) -> Key { + bond_key(bond_id) + .push(&BOND_AMOUNT_STORAGE_KEY) + .expect("Cannot obtain storage key") +} + +/// Storage key for the remaining amount in the bond with a given ID (source and +/// validator) +pub fn bond_remaining_key(bond_id: &BondId) -> Key { + bond_key(bond_id) + .push(&BOND_REMAINING_STORAGE_KEY) + .expect("Cannot obtain storage key") +} + /// Is storage key for a bond? pub fn is_bond_key(key: &Key) -> Option { match &key.segments[..] { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 57f9edcc9ea..5bfc2db952d 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,12 +5,12 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; -use std::ops::{Add}; +use std::ops::Add; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use namada_core::types::address::Address; use namada_core::ledger::storage_api::collections::lazy_map::LazyMap; use namada_core::ledger::storage_api::collections::LazyCollection; +use namada_core::types::address::Address; use namada_core::types::key::common; use namada_core::types::storage::Epoch; use namada_core::types::token; @@ -32,14 +32,14 @@ pub type ValidatorConsensusKeys_NEW = crate::epoched_new::Epoched< pub type ValidatorStates_NEW = crate::epoched_new::Epoched< ValidatorState, crate::epoched_new::OffsetPipelineLen, - 0 + 0, >; /// Epoched validator sets. pub type ValidatorSets_NEW = crate::epoched_new::Epoched< ValidatorSet, crate::epoched_new::OffsetPipelineLen, - 0 + 0, >; /// Epoched validator's deltas. @@ -50,11 +50,25 @@ pub type ValidatorDeltas_NEW = crate::epoched_new::EpochedDelta< 21, >; -/// Epoched validator's bonds -pub type Bonds_NEW = crate::epoched_new::Epoched< - Bond_NEW, +/// Epoched total deltas. +pub type TotalDeltas_NEW = crate::epoched_new::EpochedDelta< + token::Change, // TODO: check the offsets - crate::epoched_new::OffsetUnbondingLen, + crate::epoched_new::OffsetPipelineLen, + 21, +>; + +/// Epoched validator commission rate +pub type CommissionRates_NEW = crate::epoched_new::Epoched< + Decimal, + crate::epoched_new::OffsetPipelineLen, + 2, +>; + +/// Epoched validator's bonds +pub type Bonds_NEW = crate::epoched_new::EpochedDelta< + Decimal, + crate::epoched_new::OffsetPipelineLen, 21, >; @@ -233,7 +247,7 @@ pub struct Bond { pub neg_deltas: token::Amount, } -type Bond_NEW = LazyMap>>; +pub type Bond_NEW = LazyMap<(Epoch, Option), token::Amount>; /// An unbond contains unbonded tokens from a validator's self-bond or a /// delegation from a regular account to a validator. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 6e6ca784378..f6fa285b2ba 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -3,13 +3,9 @@ use std::borrow::Cow; use std::collections::HashMap; use std::convert::TryFrom; -use std::fmt::{Debug}; - +use std::fmt::Debug; use std::marker::PhantomData; - - - use namada_core::types::address::Address; use namada_core::types::key::{common, PublicKeyTmRawHash}; use namada_core::types::storage::Epoch; @@ -1342,7 +1338,8 @@ impl Validate { for slash in &slashes { if slash.epoch >= *start_epoch { let raw_delta: i128 = *delta; - let current_slashed = decimal_mult_i128(slash.rate, raw_delta); + let current_slashed = + decimal_mult_i128(slash.rate, raw_delta); *delta -= current_slashed; } } @@ -1533,7 +1530,8 @@ impl Validate { && slash.epoch <= *end_epoch { let raw_delta: i128 = *delta; - let current_slashed = decimal_mult_i128(slash.rate, raw_delta); + let current_slashed = + decimal_mult_i128(slash.rate, raw_delta); *delta -= current_slashed; } } diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index dd23d92356d..48dd711efd9 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -2,6 +2,9 @@ pub mod vp; +pub use namada_core::ledger::storage_api; +pub use namada_core::types::key::common; +pub use namada_core::types::token; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::storage::*; @@ -14,11 +17,6 @@ use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Epoch; -pub use namada_core::types::key::common; -pub use namada_core::types::token; -pub use namada_core::ledger::storage_api; - - /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -70,14 +68,12 @@ pub fn init_genesis_storage_NEW( .expect("Initialize PoS genesis storage"); } - /// Alias for a PoS type with the same name with concrete type parameters pub type ValidatorConsensusKeys = namada_proof_of_stake::types::ValidatorConsensusKeys; /// Alias for a PoS type with the same name with concrete type parameters -pub type ValidatorDeltas = - namada_proof_of_stake::types::ValidatorDeltas; +pub type ValidatorDeltas = namada_proof_of_stake::types::ValidatorDeltas; /// Alias for a PoS type with the same name with concrete type parameters pub type Bonds = namada_proof_of_stake::types::Bonds; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 11a744a6323..49e5559b548 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -33,7 +33,7 @@ use crate::ledger::pos::{ is_validator_max_commission_rate_change_key, is_validator_state_key, }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::ledger::storage_api::{StorageRead}; +use crate::ledger::storage_api::StorageRead; use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; diff --git a/shared/src/lib.rs b/shared/src/lib.rs index a05308fab48..8c514b8f9d2 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -18,7 +18,6 @@ pub use { tendermint_abcipp as tendermint, tendermint_proto_abcipp as tendermint_proto, }; - pub use {namada_core as core, namada_proof_of_stake as proof_of_stake}; pub mod ledger; pub use namada_core::proto; diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index f49f9f379c8..11a7af55335 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -1,3 +1,3 @@ //! Cryptographic keys -pub use namada_core::types::key::*; \ No newline at end of file +pub use namada_core::types::key::*; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index e556d684a2e..cb8bdbd6483 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -99,8 +99,8 @@ //! - add slashes use namada::ledger::pos::namada_proof_of_stake::PosBase; -use namada::proof_of_stake::storage::GenesisValidator; use namada::proof_of_stake::parameters::PosParams; +use namada::proof_of_stake::storage::GenesisValidator; use namada::types::storage::Epoch; use crate::tx::tx_host_env; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 21c3ee202fd..45b8cf41a48 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -40,6 +40,25 @@ impl Ctx { ) } + /// NEW: Self-bond tokens to a validator when `source` is `None` or equal to + /// the `validator` address, or delegate tokens from the `source` to the + /// `validator`. + pub fn bond_tokens_new( + &mut self, + source: Option<&Address>, + validator: &Address, + amount: token::Amount, + ) -> TxResult { + let current_epoch = self.get_block_epoch()?; + namada_proof_of_stake::PosActions::bond_tokens( + self, + source, + validator, + amount, + current_epoch, + ) + } + /// Unbond self-bonded tokens from a validator when `source` is `None` or /// equal to the `validator` address, or unbond delegated tokens from /// the `source` to the `validator`.