diff --git a/core/src/epoch_snapshot.rs b/core/src/epoch_snapshot.rs index 2e7f32a..cd3035e 100644 --- a/core/src/epoch_snapshot.rs +++ b/core/src/epoch_snapshot.rs @@ -23,18 +23,21 @@ pub struct EpochSnapshot { /// The NCN epoch for which the Epoch snapshot is valid ncn_epoch: PodU64, - /// Slot Epoch snapshot was created - slot_created: PodU64, - /// Bump seed for the PDA bump: u8, + /// Slot Epoch snapshot was created + slot_created: PodU64, + slot_finalized: PodU64, + ncn_fees: Fees, operator_count: PodU64, operators_registered: PodU64, + valid_operator_vault_delegations: PodU64, /// Counted as each delegate gets added + ///TODO What happens if `finalized() && total_votes() == 0`? total_votes: PodU128, /// Reserved space @@ -49,6 +52,7 @@ impl EpochSnapshot { pub fn new( ncn: Pubkey, ncn_epoch: u64, + bump: u8, current_slot: u64, ncn_fees: Fees, num_operators: u64, @@ -57,10 +61,12 @@ impl EpochSnapshot { ncn, ncn_epoch: PodU64::from(ncn_epoch), slot_created: PodU64::from(current_slot), - bump: 0, + slot_finalized: PodU64::from(0), + bump, ncn_fees, operator_count: PodU64::from(num_operators), operators_registered: PodU64::from(0), + valid_operator_vault_delegations: PodU64::from(0), total_votes: PodU128::from(0), reserved: [0; 128], } @@ -121,6 +127,57 @@ impl EpochSnapshot { } Ok(()) } + + pub fn operator_count(&self) -> u64 { + self.operator_count.into() + } + + pub fn operators_registered(&self) -> u64 { + self.operators_registered.into() + } + + pub fn valid_operator_vault_delegations(&self) -> u64 { + self.valid_operator_vault_delegations.into() + } + + pub fn total_votes(&self) -> u128 { + self.total_votes.into() + } + + pub fn finalized(&self) -> bool { + self.operators_registered() == self.operator_count() + } + + pub fn increment_operator_registration( + &mut self, + current_slot: u64, + vault_operator_delegations: u64, + votes: u128, + ) -> Result<(), TipRouterError> { + self.operators_registered = PodU64::from( + self.operators_registered() + .checked_add(1) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + + self.valid_operator_vault_delegations = PodU64::from( + self.valid_operator_vault_delegations() + .checked_add(vault_operator_delegations) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + + self.total_votes = PodU128::from( + self.total_votes() + .checked_add(votes) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + + if self.finalized() { + self.slot_finalized = PodU64::from(current_slot); + } + + Ok(()) + } } // PDA'd ["OPERATOR_SNAPSHOT", OPERATOR, NCN, NCN_EPOCH_SLOT] @@ -130,22 +187,22 @@ pub struct OperatorSnapshot { operator: Pubkey, ncn: Pubkey, ncn_epoch: PodU64, - slot_created: PodU64, - bump: u8, + slot_created: PodU64, + slot_finalized: PodU64, + is_active: PodBool, operator_fee_bps: PodU16, - total_votes: PodU128, + vault_operator_delegation_count: PodU64, + vault_operator_delegations_registered: PodU64, - num_vault_operator_delegations: PodU16, - vault_operator_delegations_registered: PodU16, + total_votes: PodU128, - slot_set: PodU64, //TODO check upper limit of vaults - vault_operator_delegations: [VaultOperatorDelegationSnapshot; 32], + vault_operator_delegations: [VaultOperatorDelegationSnapshot; 64], reserved: [u8; 128], } @@ -158,27 +215,63 @@ impl OperatorSnapshot { operator: Pubkey, ncn: Pubkey, ncn_epoch: u64, - is_active: bool, + bump: u8, current_slot: u64, + is_active: bool, operator_fee_bps: u16, + vault_operator_delegation_count: u64, ) -> Self { Self { operator, ncn, ncn_epoch: PodU64::from(ncn_epoch), - slot_created: PodU64::from(0), - bump: 0, + bump, + slot_created: PodU64::from(current_slot), + slot_finalized: PodU64::from(0), operator_fee_bps: PodU16::from(operator_fee_bps), total_votes: PodU128::from(0), is_active: PodBool::from(is_active), - num_vault_operator_delegations: PodU16::from(0), - vault_operator_delegations_registered: PodU16::from(0), - slot_set: PodU64::from(current_slot), - vault_operator_delegations: [VaultOperatorDelegationSnapshot::default(); 32], + vault_operator_delegation_count: PodU64::from(vault_operator_delegation_count), + vault_operator_delegations_registered: PodU64::from(0), + vault_operator_delegations: [VaultOperatorDelegationSnapshot::default(); 64], reserved: [0; 128], } } + pub fn new_active( + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: u64, + bump: u8, + current_slot: u64, + operator_fee_bps: u16, + vault_count: u64, + ) -> Self { + Self::new( + operator, + ncn, + ncn_epoch, + bump, + current_slot, + true, + operator_fee_bps, + vault_count, + ) + } + + pub fn new_inactive( + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: u64, + bump: u8, + current_slot: u64, + ) -> Self { + let mut snapshot = Self::new(operator, ncn, ncn_epoch, bump, current_slot, true, 0, 0); + + snapshot.slot_finalized = PodU64::from(current_slot); + snapshot + } + pub fn seeds(operator: &Pubkey, ncn: &Pubkey, ncn_epoch: u64) -> Vec> { Vec::from_iter( [ diff --git a/core/src/error.rs b/core/src/error.rs index 6f4b6b7..4d6cca0 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -44,6 +44,8 @@ pub enum TipRouterError { WeightTableNotFinalized, #[error("Weight not found")] WeightNotFound, + #[error("No operators in ncn")] + NoOperators, } impl DecodeError for TipRouterError { diff --git a/program/src/initialize_epoch_snapshot.rs b/program/src/initialize_epoch_snapshot.rs index b525d1c..92c2bb4 100644 --- a/program/src/initialize_epoch_snapshot.rs +++ b/program/src/initialize_epoch_snapshot.rs @@ -94,14 +94,25 @@ pub fn process_initialize_epoch_snapshot( ncn_account.operator_count() }; + if operator_count == 0 { + msg!("No operators to snapshot"); + return Err(TipRouterError::NoOperators.into()); + } + let mut epoch_snapshot_data: std::cell::RefMut<'_, &mut [u8]> = epoch_snapshot.try_borrow_mut_data()?; epoch_snapshot_data[0] = EpochSnapshot::DISCRIMINATOR; let epoch_snapshot_account = EpochSnapshot::try_from_slice_unchecked_mut(&mut epoch_snapshot_data)?; - *epoch_snapshot_account = - EpochSnapshot::new(*ncn.key, ncn_epoch, current_slot, ncn_fees, operator_count); + *epoch_snapshot_account = EpochSnapshot::new( + *ncn.key, + ncn_epoch, + epoch_snapshot_bump, + current_slot, + ncn_fees, + operator_count, + ); Ok(()) } diff --git a/program/src/initialize_operator_snapshot.rs b/program/src/initialize_operator_snapshot.rs index cf41f31..398c9d4 100644 --- a/program/src/initialize_operator_snapshot.rs +++ b/program/src/initialize_operator_snapshot.rs @@ -8,11 +8,8 @@ use jito_restaking_core::{ }; use jito_tip_router_core::{ epoch_snapshot::{EpochSnapshot, OperatorSnapshot}, - error::TipRouterError, - fees, loaders::load_ncn_epoch, ncn_config::NcnConfig, - weight_table::WeightTable, }; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, @@ -99,16 +96,13 @@ pub fn process_initialize_operator_snapshot( &operator_snapshot_seeds, )?; - let ncn_fees: fees::Fees = { - let ncn_config_data = ncn_config.data.borrow(); - let ncn_config_account = NcnConfig::try_from_slice_unchecked(&ncn_config_data)?; - ncn_config_account.fees - }; - - let operator_count: u64 = { - let ncn_data = ncn.data.borrow(); - let ncn_account = Ncn::try_from_slice_unchecked(&ncn_data)?; - ncn_account.operator_count() + let (operator_fee_bps, vault_count): (u16, u64) = { + let operator_data = operator.data.borrow(); + let operator_account = Operator::try_from_slice_unchecked(&operator_data)?; + ( + operator_account.operator_fee_bps.into(), + operator_account.vault_count(), + ) }; let mut operator_snapshot_data: std::cell::RefMut<'_, &mut [u8]> = @@ -117,8 +111,34 @@ pub fn process_initialize_operator_snapshot( let operator_snapshot_account = OperatorSnapshot::try_from_slice_unchecked_mut(&mut operator_snapshot_data)?; - *operator_snapshot_account = - OperatorSnapshot::new(*ncn.key, ncn_epoch, current_slot, ncn_fees, operator_count); + *operator_snapshot_account = if is_active { + OperatorSnapshot::new_active( + *operator.key, + *ncn.key, + ncn_epoch, + operator_snapshot_bump, + current_slot, + operator_fee_bps, + vault_count, + ) + } else { + OperatorSnapshot::new_inactive( + *operator.key, + *ncn.key, + ncn_epoch, + operator_snapshot_bump, + current_slot, + ) + }; + + // Increment operator registration for an inactive operator + if !is_active { + let mut epoch_snapshot_data = epoch_snapshot.try_borrow_mut_data()?; + let epoch_snapshot_account = + EpochSnapshot::try_from_slice_unchecked_mut(&mut epoch_snapshot_data)?; + + epoch_snapshot_account.increment_operator_registration(current_slot, 0, 0)?; + } Ok(()) } diff --git a/program/src/initialize_weight_table.rs b/program/src/initialize_weight_table.rs index 1895f24..384a8be 100644 --- a/program/src/initialize_weight_table.rs +++ b/program/src/initialize_weight_table.rs @@ -7,8 +7,7 @@ use jito_jsm_core::{ }; use jito_restaking_core::{config::Config, ncn::Ncn}; use jito_tip_router_core::{ - error::TipRouterError, loaders::load_ncn_epoch, ncn_config::NcnConfig, - weight_table::WeightTable, + loaders::load_ncn_epoch, ncn_config::NcnConfig, weight_table::WeightTable, }; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg,