From b1dd01e085d1d0178b8fbfcd4b6a4c8296b21717 Mon Sep 17 00:00:00 2001 From: Christian Krueger Date: Mon, 11 Nov 2024 17:07:25 -0800 Subject: [PATCH] good work --- core/src/discriminators.rs | 1 + core/src/epoch_snapshot.rs | 229 ++++++++++++++---- core/src/instruction.rs | 20 ++ program/src/initialize_operator_snapshot.rs | 45 ++-- ...lize_vault_operator_delegation_snapshot.rs | 200 +++++++++++++++ program/src/lib.rs | 12 + 6 files changed, 436 insertions(+), 71 deletions(-) create mode 100644 program/src/initialize_vault_operator_delegation_snapshot.rs diff --git a/core/src/discriminators.rs b/core/src/discriminators.rs index 7df10f6..2bfa55d 100644 --- a/core/src/discriminators.rs +++ b/core/src/discriminators.rs @@ -4,4 +4,5 @@ pub enum Discriminators { WeightTable = 2, EpochSnapshot = 3, OperatorSnapshot = 4, + VaultOperatorDelegationSnapshot = 5, } diff --git a/core/src/epoch_snapshot.rs b/core/src/epoch_snapshot.rs index cd3035e..251ad99 100644 --- a/core/src/epoch_snapshot.rs +++ b/core/src/epoch_snapshot.rs @@ -3,14 +3,10 @@ use jito_bytemuck::{ types::{PodBool, PodU128, PodU16, PodU64}, AccountDeserialize, Discriminator, }; -use jito_vault_core::delegation_state::DelegationState; use shank::{ShankAccount, ShankType}; use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; -use spl_math::precise_number::PreciseNumber; -use crate::{ - discriminators::Discriminators, error::TipRouterError, fees::Fees, weight_table::WeightTable, -}; +use crate::{discriminators::Discriminators, error::TipRouterError, fees::Fees}; // PDA'd ["EPOCH_SNAPSHOT", NCN, NCN_EPOCH_SLOT] #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] @@ -198,11 +194,10 @@ pub struct OperatorSnapshot { vault_operator_delegation_count: PodU64, vault_operator_delegations_registered: PodU64, + valid_operator_vault_delegations: PodU64, total_votes: PodU128, - //TODO check upper limit of vaults - vault_operator_delegations: [VaultOperatorDelegationSnapshot; 64], reserved: [u8; 128], } @@ -228,12 +223,12 @@ impl OperatorSnapshot { 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), + operator_fee_bps: PodU16::from(operator_fee_bps), vault_operator_delegation_count: PodU64::from(vault_operator_delegation_count), vault_operator_delegations_registered: PodU64::from(0), - vault_operator_delegations: [VaultOperatorDelegationSnapshot::default(); 64], + valid_operator_vault_delegations: PodU64::from(0), + total_votes: PodU128::from(0), reserved: [0; 128], } } @@ -330,85 +325,219 @@ impl OperatorSnapshot { } Ok(()) } + + pub fn vault_operator_delegation_count(&self) -> u64 { + self.vault_operator_delegation_count.into() + } + + pub fn vault_operator_delegations_registered(&self) -> u64 { + self.vault_operator_delegations_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.vault_operator_delegations_registered() == self.vault_operator_delegation_count() + } + + pub fn increment_vault_operator_delegation_registration( + &mut self, + current_slot: u64, + vault_operator_delegations: u64, + votes: u128, + ) -> Result<(), TipRouterError> { + self.vault_operator_delegations_registered = PodU64::from( + self.vault_operator_delegations_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(()) + } } -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +// PDA'd ["OPERATOR_SNAPSHOT", VAULT, OPERATOR, NCN, NCN_EPOCH_SLOT] +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] #[repr(C)] pub struct VaultOperatorDelegationSnapshot { vault: Pubkey, + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: PodU64, + bump: u8, + + slot_created: PodU64, + + is_active: PodBool, + st_mint: Pubkey, total_security: PodU64, total_votes: PodU128, - slot_set: PodU64, + reserved: [u8; 128], } -impl Default for VaultOperatorDelegationSnapshot { - fn default() -> Self { - Self { - vault: Pubkey::default(), - st_mint: Pubkey::default(), - total_security: PodU64::from(0), - total_votes: PodU128::from(0), - slot_set: PodU64::from(0), - reserved: [0; 128], - } - } +impl Discriminator for VaultOperatorDelegationSnapshot { + const DISCRIMINATOR: u8 = Discriminators::VaultOperatorDelegationSnapshot as u8; } impl VaultOperatorDelegationSnapshot { pub fn new( vault: Pubkey, + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: u64, + bump: u8, + current_slot: u64, + is_active: bool, st_mint: Pubkey, total_security: u64, total_votes: u128, - current_slot: u64, ) -> Self { Self { vault, + operator, + ncn, + ncn_epoch: PodU64::from(ncn_epoch), + bump, + slot_created: PodU64::from(current_slot), + is_active: PodBool::from(is_active), st_mint, total_security: PodU64::from(total_security), total_votes: PodU128::from(total_votes), - slot_set: PodU64::from(current_slot), reserved: [0; 128], } } - pub fn create_snapshot( + pub fn new_active( vault: Pubkey, - st_mint: Pubkey, - delegation_state: &DelegationState, - weight_table: &WeightTable, + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: u64, + bump: u8, current_slot: u64, - ) -> Result { - let total_security = delegation_state.total_security()?; - let precise_total_security = PreciseNumber::new(total_security as u128) - .ok_or(TipRouterError::NewPreciseNumberError)?; - - let precise_weight = weight_table.get_precise_weight(&st_mint)?; - - let precise_total_votes = precise_total_security - .checked_mul(&precise_weight) - .ok_or(TipRouterError::ArithmeticOverflow)?; - - let total_votes = precise_total_votes - .to_imprecise() - .ok_or(TipRouterError::CastToImpreciseNumberError)?; - - Ok(Self::new( + st_mint: Pubkey, + total_security: u64, + total_votes: u128, + ) -> Self { + Self::new( vault, + operator, + ncn, + ncn_epoch, + bump, + current_slot, + true, st_mint, total_security, total_votes, + ) + } + + pub fn new_inactive( + vault: Pubkey, + operator: Pubkey, + ncn: Pubkey, + ncn_epoch: u64, + bump: u8, + current_slot: u64, + st_mint: Pubkey, + ) -> Self { + Self::new( + vault, + operator, + ncn, + ncn_epoch, + bump, current_slot, - )) + false, + st_mint, + 0, + 0, + ) } - pub fn is_empty(&self) -> bool { - self.slot_set.eq(&PodU64::from(0)) + pub fn seeds(vault: &Pubkey, operator: &Pubkey, ncn: &Pubkey, ncn_epoch: u64) -> Vec> { + Vec::from_iter( + [ + b"VAULT_OPERATOR_DELEGATION_SNAPSHOT".to_vec(), + vault.to_bytes().to_vec(), + operator.to_bytes().to_vec(), + ncn.to_bytes().to_vec(), + ncn_epoch.to_le_bytes().to_vec(), + ] + .iter() + .cloned(), + ) } - pub fn total_votes(&self) -> u128 { - self.total_votes.into() + pub fn find_program_address( + program_id: &Pubkey, + vault: &Pubkey, + operator: &Pubkey, + ncn: &Pubkey, + ncn_epoch: u64, + ) -> (Pubkey, u8, Vec>) { + let seeds = Self::seeds(vault, operator, ncn, ncn_epoch); + let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_slice()).collect(); + let (pda, bump) = Pubkey::find_program_address(&seeds_iter, program_id); + (pda, bump, seeds) + } + + pub fn load( + program_id: &Pubkey, + vault: &Pubkey, + operator: &Pubkey, + ncn: &Pubkey, + ncn_epoch: u64, + vault_operator_delegation_snapshot: &AccountInfo, + expect_writable: bool, + ) -> Result<(), ProgramError> { + if vault_operator_delegation_snapshot.owner.ne(program_id) { + msg!("Operator Snapshot account has an invalid owner"); + return Err(ProgramError::InvalidAccountOwner); + } + if vault_operator_delegation_snapshot.data_is_empty() { + msg!("Operator Snapshot account data is empty"); + return Err(ProgramError::InvalidAccountData); + } + if expect_writable && !vault_operator_delegation_snapshot.is_writable { + msg!("Operator Snapshot account is not writable"); + return Err(ProgramError::InvalidAccountData); + } + if vault_operator_delegation_snapshot.data.borrow()[0].ne(&Self::DISCRIMINATOR) { + msg!("Operator Snapshot account discriminator is invalid"); + return Err(ProgramError::InvalidAccountData); + } + if vault_operator_delegation_snapshot + .key + .ne(&Self::find_program_address(program_id, vault, operator, ncn, ncn_epoch).0) + { + msg!("Operator Snapshot account is not at the correct PDA"); + return Err(ProgramError::InvalidAccountData); + } + Ok(()) } } diff --git a/core/src/instruction.rs b/core/src/instruction.rs index 55210dd..df12d59 100644 --- a/core/src/instruction.rs +++ b/core/src/instruction.rs @@ -101,4 +101,24 @@ pub enum TipRouterInstruction { InitializeOperatorSnapshot{ first_slot_of_ncn_epoch: Option, }, + + /// Initializes the Vault Operator Delegation Snapshot + #[account(0, name = "ncn_config")] + #[account(1, name = "restaking_config")] + #[account(2, name = "ncn")] + #[account(3, name = "operator")] + #[account(4, name = "vault")] + #[account(5, name = "vault_ncn_ticket")] + #[account(6, name = "ncn_vault_ticket")] + #[account(7, name = "vault_operator_delegation")] + #[account(8, name = "weight_table")] + #[account(9, writable, name = "epoch_snapshot")] + #[account(10, writable, name = "operator_snapshot")] + #[account(11, writable, name = "vault_operator_delegation_snapshot")] + #[account(12, writable, signer, name = "payer")] + #[account(13, name = "restaking_program_id")] + #[account(14, name = "system_program")] + InitializeVaultOperatorDelegationSnapshot{ + first_slot_of_ncn_epoch: Option, + }, } diff --git a/program/src/initialize_operator_snapshot.rs b/program/src/initialize_operator_snapshot.rs index 398c9d4..dbc6726 100644 --- a/program/src/initialize_operator_snapshot.rs +++ b/program/src/initialize_operator_snapshot.rs @@ -56,19 +56,6 @@ pub fn process_initialize_operator_snapshot( EpochSnapshot::load(program_id, ncn.key, ncn_epoch, epoch_snapshot, true)?; - let is_active: bool = { - let ncn_operator_state_data = ncn_operator_state.data.borrow(); - let ncn_operator_state_account = - NcnOperatorState::try_from_slice_unchecked(&ncn_operator_state_data)?; - - ncn_operator_state_account - .ncn_opt_in_state - .is_active(current_slot, ncn_epoch_length) - && ncn_operator_state_account - .operator_opt_in_state - .is_active(current_slot, ncn_epoch_length) - }; - let (operator_snapshot_pubkey, operator_snapshot_bump, mut operator_snapshot_seeds) = EpochSnapshot::find_program_address(program_id, ncn.key, ncn_epoch); operator_snapshot_seeds.push(vec![operator_snapshot_bump]); @@ -96,22 +83,38 @@ pub fn process_initialize_operator_snapshot( &operator_snapshot_seeds, )?; - 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 is_active: bool = { + let ncn_operator_state_data = ncn_operator_state.data.borrow(); + let ncn_operator_state_account = + NcnOperatorState::try_from_slice_unchecked(&ncn_operator_state_data)?; + + let ncn_operator_okay = ncn_operator_state_account + .ncn_opt_in_state + .is_active(current_slot, ncn_epoch_length); + + let operator_ncn_okay = ncn_operator_state_account + .operator_opt_in_state + .is_active(current_slot, ncn_epoch_length); + + ncn_operator_okay && operator_ncn_okay }; let mut operator_snapshot_data: std::cell::RefMut<'_, &mut [u8]> = operator_snapshot.try_borrow_mut_data()?; - operator_snapshot_data[0] = EpochSnapshot::DISCRIMINATOR; + operator_snapshot_data[0] = OperatorSnapshot::DISCRIMINATOR; let operator_snapshot_account = OperatorSnapshot::try_from_slice_unchecked_mut(&mut operator_snapshot_data)?; *operator_snapshot_account = if is_active { + 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(), + ) + }; + OperatorSnapshot::new_active( *operator.key, *ncn.key, diff --git a/program/src/initialize_vault_operator_delegation_snapshot.rs b/program/src/initialize_vault_operator_delegation_snapshot.rs new file mode 100644 index 0000000..fd40193 --- /dev/null +++ b/program/src/initialize_vault_operator_delegation_snapshot.rs @@ -0,0 +1,200 @@ +use jito_bytemuck::{AccountDeserialize, Discriminator}; +use jito_jsm_core::{ + create_account, + loader::{load_signer, load_system_account, load_system_program}, +}; +use jito_restaking_core::{ + config::Config, ncn::Ncn, ncn_vault_ticket::NcnVaultTicket, operator::Operator, +}; +use jito_tip_router_core::{ + epoch_snapshot::{EpochSnapshot, OperatorSnapshot, VaultOperatorDelegationSnapshot}, + loaders::load_ncn_epoch, + ncn_config::NcnConfig, + weight_table::WeightTable, +}; +use jito_vault_core::{ + vault::Vault, vault_ncn_ticket::VaultNcnTicket, + vault_operator_delegation::VaultOperatorDelegation, +}; +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, + program_error::ProgramError, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, +}; + +pub fn process_initialize_vault_operator_delegation_snapshot( + program_id: &Pubkey, + accounts: &[AccountInfo], + first_slot_of_ncn_epoch: Option, +) -> ProgramResult { + let [ncn_config, restaking_config, ncn, operator, vault, vault_ncn_ticket, ncn_vault_ticket, vault_operator_delegation, weight_table, epoch_snapshot, operator_snapshot, vault_operator_delegation_snapshot, payer, restaking_program_id, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if restaking_program_id.key.ne(&jito_restaking_program::id()) { + msg!("Incorrect restaking program ID"); + return Err(ProgramError::InvalidAccountData); + } + + NcnConfig::load(program_id, ncn.key, ncn_config, false)?; + Config::load(restaking_program_id.key, restaking_config, false)?; + Ncn::load(restaking_program_id.key, ncn, false)?; + Operator::load(restaking_program_id.key, operator, false)?; + Vault::load(restaking_program_id.key, vault, false)?; + + VaultNcnTicket::load( + restaking_program_id.key, + vault_ncn_ticket, + vault, + ncn, + false, + )?; + NcnVaultTicket::load( + restaking_program_id.key, + ncn_vault_ticket, + ncn, + vault, + false, + )?; + + //TODO check that st mint is supported? + //TODO may not exist + if !vault_operator_delegation.data_is_empty() { + VaultOperatorDelegation::load( + restaking_program_id.key, + vault_operator_delegation, + vault, + operator, + false, + )?; + } + + load_system_account(vault_operator_delegation_snapshot, true)?; + load_system_program(system_program)?; + //TODO check that it is not writable + load_signer(payer, false)?; + + let current_slot = Clock::get()?.slot; + let (ncn_epoch, ncn_epoch_length) = + load_ncn_epoch(restaking_config, current_slot, first_slot_of_ncn_epoch)?; + + WeightTable::load(program_id, weight_table, ncn, ncn_epoch, false)?; + EpochSnapshot::load(program_id, ncn.key, ncn_epoch, epoch_snapshot, true)?; + OperatorSnapshot::load( + program_id, + operator.key, + ncn.key, + ncn_epoch, + epoch_snapshot, + true, + )?; + + let ( + vault_operator_delegation_snapshot_pubkey, + vault_operator_delegation_snapshot_bump, + mut vault_operator_delegation_snapshot_seeds, + ) = VaultOperatorDelegationSnapshot::find_program_address( + program_id, + vault.key, + operator.key, + ncn.key, + ncn_epoch, + ); + vault_operator_delegation_snapshot_seeds.push(vec![vault_operator_delegation_snapshot_bump]); + + if vault_operator_delegation_snapshot_pubkey.ne(operator_snapshot.key) { + msg!("Incorrect vault operator delegation snapshot PDA"); + return Err(ProgramError::InvalidAccountData); + } + + msg!( + "Initializing vault operator delegation snapshot {} for NCN: {} at epoch: {}", + epoch_snapshot.key, + ncn.key, + ncn_epoch + ); + create_account( + payer, + operator_snapshot, + system_program, + program_id, + &Rent::get()?, + 8_u64 + .checked_add(std::mem::size_of::() as u64) + .unwrap(), + &vault_operator_delegation_snapshot_seeds, + )?; + + let is_active: bool = { + let vault_ncn_ticket_data = vault_ncn_ticket.data.borrow(); + let vault_ncn_ticket_account = + VaultNcnTicket::try_from_slice_unchecked(&vault_ncn_ticket_data)?; + + let ncn_vault_ticket_data = ncn_vault_ticket.data.borrow(); + let ncn_vault_ticket_account = + NcnVaultTicket::try_from_slice_unchecked(&ncn_vault_ticket_data)?; + + let vault_ncn_okay = vault_ncn_ticket_account + .state + .is_active(current_slot, ncn_epoch_length); + + let ncn_vault_okay = ncn_vault_ticket_account + .state + .is_active(current_slot, ncn_epoch_length); + + let delegation_dne = vault_operator_delegation.data_is_empty(); + + vault_ncn_okay && ncn_vault_okay && delegation_dne + }; + + let mut vault_operator_delegation_snapshot_data: std::cell::RefMut<'_, &mut [u8]> = + operator_snapshot.try_borrow_mut_data()?; + vault_operator_delegation_snapshot_data[0] = VaultOperatorDelegationSnapshot::DISCRIMINATOR; + let vault_operator_delegation_snapshot_account = + VaultOperatorDelegationSnapshot::try_from_slice_unchecked_mut( + &mut vault_operator_delegation_snapshot_data, + )?; + + *vault_operator_delegation_snapshot_account = if is_active { + 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(), + ) + }; + + //TODO Ending here for the day + VaultOperatorDelegationSnapshot::new_active( + *vault.key, + *operator.key, + *ncn.key, + ncn_epoch, + vault_operator_delegation_snapshot_bump, + current_slot, + operator_fee_bps, + vault_count, + ) + } else { + VaultOperatorDelegationSnapshot::new_inactive( + *operator.key, + *ncn.key, + ncn_epoch, + vault_operator_delegation_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/lib.rs b/program/src/lib.rs index 6b2e4c6..08658d3 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -2,6 +2,7 @@ mod admin_update_weight_table; mod initialize_epoch_snapshot; mod initialize_ncn_config; mod initialize_operator_snapshot; +mod initialize_vault_operator_delegation_snapshot; mod initialize_weight_table; mod set_config_fees; mod set_new_admin; @@ -22,6 +23,7 @@ use crate::{ initialize_epoch_snapshot::process_initialize_epoch_snapshot, initialize_ncn_config::process_initialize_ncn_config, initialize_operator_snapshot::process_initialize_operator_snapshot, + initialize_vault_operator_delegation_snapshot::process_initialize_vault_operator_delegation_snapshot, initialize_weight_table::process_initialize_weight_table, set_config_fees::process_set_config_fees, }; @@ -90,6 +92,16 @@ pub fn process_instruction( msg!("Instruction: InitializeOperatorSnapshot"); process_initialize_operator_snapshot(program_id, accounts, first_slot_of_ncn_epoch) } + TipRouterInstruction::InitializeVaultOperatorDelegationSnapshot { + first_slot_of_ncn_epoch, + } => { + msg!("Instruction: InitializeVaultOperatorDelegationSnapshot"); + process_initialize_vault_operator_delegation_snapshot( + program_id, + accounts, + first_slot_of_ncn_epoch, + ) + } // ------------------------------------------ // Update // ------------------------------------------