From 292c4e20c856fa139494c76951c3a8b99e02c973 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 16:14:34 -0500 Subject: [PATCH 01/22] Squashed commit of the following: commit f76edb0e13d271d2ccfdcfbce59a59b7d024912d Merge: c40c13f 3332570 Author: Lucas B Date: Tue Jul 30 16:12:08 2024 -0500 Merge branch 'master' into lb/enqueue_withdraw commit c40c13f2c4523dddd72dbb2731c48e0fa2af9834 Author: Lucas B Date: Tue Jul 30 16:11:25 2024 -0500 more work commit 52d3df084c16288db8b7eed407c57887c0a0e6d8 Author: Lucas B Date: Thu Jul 25 21:55:57 2024 -0500 test commit ff84b23ea89547f2ae5054376f7b9e1919922931 Author: Lucas B Date: Thu Jul 25 19:35:24 2024 -0500 test ratios commit 1e95fa92d7dd378d37994774a9cf3002e81e425c Merge: 7b8fcd7 c431698 Author: Lucas B Date: Thu Jul 25 11:36:45 2024 -0500 Merge branch 'master' into lb/enqueue_withdraw commit 7b8fcd7345d36ec1d7f22d68abf3b5d437c2ac0e Author: Lucas B Date: Wed Jul 24 23:13:34 2024 -0500 Withdraws commit 6edcb51b4c43837f05f3691e2851c27403ae6669 Author: buffalu <85544055+buffalu@users.noreply.github.com> Date: Wed Jul 24 23:03:07 2024 -0500 Fix links (#1) --- core/Cargo.toml | 2 +- integration_tests/tests/vault/slash.rs | 4 +- restaking_core/src/avs_operator_ticket.rs | 1 - .../src/avs_vault_slasher_ticket.rs | 1 - restaking_core/src/avs_vault_ticket.rs | 1 - restaking_core/src/config.rs | 1 - restaking_core/src/operator.rs | 1 - restaking_core/src/operator_avs_ticket.rs | 1 - restaking_core/src/operator_vault_ticket.rs | 1 - sanitization/src/lib.rs | 25 + vault_core/src/lib.rs | 4 + vault_core/src/operator_delegation.rs | 260 ++++++++ vault_core/src/result.rs | 26 + vault_core/src/vault.rs | 95 ++- .../src/vault_avs_slasher_operator_ticket.rs | 1 - vault_core/src/vault_delegation_list.rs | 561 +++++++++++++----- .../src/vault_staker_withdraw_ticket.rs | 197 ++++++ vault_program/src/add_delegation.rs | 9 +- vault_program/src/burn_withdraw_ticket.rs | 370 ++++++++++++ vault_program/src/enqueue_withdrawal.rs | 250 +++++++- vault_program/src/lib.rs | 21 +- vault_program/src/remove_delegation.rs | 5 +- vault_program/src/slash.rs | 9 +- vault_program/src/update_delegations.rs | 52 -- vault_program/src/update_vault.rs | 75 +++ vault_sdk/src/lib.rs | 43 +- 26 files changed, 1759 insertions(+), 257 deletions(-) create mode 100644 vault_core/src/operator_delegation.rs create mode 100644 vault_core/src/vault_staker_withdraw_ticket.rs create mode 100644 vault_program/src/burn_withdraw_ticket.rs delete mode 100644 vault_program/src/update_delegations.rs create mode 100644 vault_program/src/update_vault.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 0265adaa..e5f51674 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jito-jsm-core" -description = "JSM core library" +description = "JSM Core library" version = { workspace = true } authors = { workspace = true } repository = { workspace = true } diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index a9819898..37e7e83e 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -349,7 +349,7 @@ async fn test_slash_ok() { let delegations = vault_delegation_list.delegations(); assert_eq!(delegations.len(), 1); assert_eq!(delegations[0].operator(), operator_pubkey); - assert_eq!(delegations[0].active_amount(), 10_000); + assert_eq!(delegations[0].staked_amount(), 10_000); let slasher_token_account = get_associated_token_address(&slasher.pubkey(), &token_mint.pubkey()); @@ -424,7 +424,7 @@ async fn test_slash_ok() { let delegations = delegation_list.delegations(); assert_eq!(delegations.len(), 1); assert_eq!(delegations[0].operator(), operator_pubkey); - assert_eq!(delegations[0].active_amount(), 9_900); + assert_eq!(delegations[0].staked_amount(), 9_900); let vault_avs_slasher_operator_ticket = vault_program_client .get_vault_avs_slasher_operator_ticket( diff --git a/restaking_core/src/avs_operator_ticket.rs b/restaking_core/src/avs_operator_ticket.rs index 9935406b..38f01ee5 100644 --- a/restaking_core/src/avs_operator_ticket.rs +++ b/restaking_core/src/avs_operator_ticket.rs @@ -113,7 +113,6 @@ impl AvsOperatorTicket { return Err(RestakingCoreError::AvsOperatorTicketInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let avs_operator_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::AvsOperatorTicketInvalidData(e.to_string()))?; if avs_operator_ticket.account_type != AccountType::AvsOperatorTicket { diff --git a/restaking_core/src/avs_vault_slasher_ticket.rs b/restaking_core/src/avs_vault_slasher_ticket.rs index 32b82396..f2d5cb0e 100644 --- a/restaking_core/src/avs_vault_slasher_ticket.rs +++ b/restaking_core/src/avs_vault_slasher_ticket.rs @@ -139,7 +139,6 @@ impl AvsVaultSlasherTicket { return Err(RestakingCoreError::AvsSlasherTicketInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let avs_slasher_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::AvsSlasherTicketInvalidData(e.to_string()))?; if avs_slasher_ticket.account_type != AccountType::AvsVaultSlasherTicket { diff --git a/restaking_core/src/avs_vault_ticket.rs b/restaking_core/src/avs_vault_ticket.rs index 11a1eb19..b8dec1f6 100644 --- a/restaking_core/src/avs_vault_ticket.rs +++ b/restaking_core/src/avs_vault_ticket.rs @@ -110,7 +110,6 @@ impl AvsVaultTicket { return Err(RestakingCoreError::AvsVaultTicketInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let avs_vault_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::AvsVaultTicketInvalidData(e.to_string()))?; if avs_vault_ticket.account_type != AccountType::AvsVaultTicket { diff --git a/restaking_core/src/config.rs b/restaking_core/src/config.rs index 5cd91636..0e19132b 100644 --- a/restaking_core/src/config.rs +++ b/restaking_core/src/config.rs @@ -102,7 +102,6 @@ impl Config { return Err(RestakingCoreError::ConfigInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let config = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::ConfigInvalidData(e.to_string()))?; if config.account_type != AccountType::Config { diff --git a/restaking_core/src/operator.rs b/restaking_core/src/operator.rs index b9fa8dca..cc783f9b 100644 --- a/restaking_core/src/operator.rs +++ b/restaking_core/src/operator.rs @@ -167,7 +167,6 @@ impl Operator { return Err(RestakingCoreError::OperatorInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let operator = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::OperatorInvalidData(e.to_string()))?; if operator.account_type != AccountType::Operator { diff --git a/restaking_core/src/operator_avs_ticket.rs b/restaking_core/src/operator_avs_ticket.rs index bb4d1501..f27c94e2 100644 --- a/restaking_core/src/operator_avs_ticket.rs +++ b/restaking_core/src/operator_avs_ticket.rs @@ -109,7 +109,6 @@ impl OperatorAvsTicket { return Err(RestakingCoreError::OperatorAvsTicketInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let avs_vault_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::OperatorAvsTicketInvalidData(e.to_string()))?; if avs_vault_ticket.account_type != AccountType::OperatorAvsTicket { diff --git a/restaking_core/src/operator_vault_ticket.rs b/restaking_core/src/operator_vault_ticket.rs index b4d40beb..92477621 100644 --- a/restaking_core/src/operator_vault_ticket.rs +++ b/restaking_core/src/operator_vault_ticket.rs @@ -118,7 +118,6 @@ impl OperatorVaultTicket { return Err(RestakingCoreError::OperatorVaultTicketInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let avs_vault_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| RestakingCoreError::OperatorVaultTicketInvalidData(e.to_string()))?; if avs_vault_ticket.account_type != AccountType::OperatorVaultTicket { diff --git a/sanitization/src/lib.rs b/sanitization/src/lib.rs index b0ad9ed2..88956071 100644 --- a/sanitization/src/lib.rs +++ b/sanitization/src/lib.rs @@ -102,6 +102,31 @@ pub fn realloc<'a, 'info>( Ok(()) } +/// Closes the program account +pub fn close_program_account<'a>( + program_id: &Pubkey, + account_to_close: &AccountInfo<'a>, + destination_account: &AccountInfo<'a>, +) -> ProgramResult { + // Check if the account is owned by the program + if account_to_close.owner != program_id { + return Err(ProgramError::IllegalOwner); + } + + **destination_account.lamports.borrow_mut() = destination_account + .lamports() + .checked_add(account_to_close.lamports()) + .ok_or(ProgramError::ArithmeticOverflow)?; + **account_to_close.lamports.borrow_mut() = 0; + + account_to_close.assign(&solana_program::system_program::id()); + let mut account_data = account_to_close.data.borrow_mut(); + let data_len = account_data.len(); + solana_program::program_memory::sol_memset(*account_data, 0, data_len); + + Ok(()) +} + #[inline(always)] pub fn assert_with_msg(v: bool, err: impl Into, msg: &str) -> ProgramResult { if v { diff --git a/vault_core/src/lib.rs b/vault_core/src/lib.rs index 39a7ee72..916b423f 100644 --- a/vault_core/src/lib.rs +++ b/vault_core/src/lib.rs @@ -1,6 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub mod config; +pub mod operator_delegation; pub mod result; pub mod vault; pub mod vault_avs_slasher_operator_ticket; @@ -8,6 +9,7 @@ pub mod vault_avs_slasher_ticket; pub mod vault_avs_ticket; pub mod vault_delegation_list; pub mod vault_operator_ticket; +pub mod vault_staker_withdraw_ticket; #[derive(Debug, Clone, Copy, PartialEq, BorshDeserialize, BorshSerialize)] #[repr(u32)] @@ -19,4 +21,6 @@ enum AccountType { VaultAvsTicket, VaultDelegationList, VaultAvsSlasherOperatorTicket, + VaultStakerWithdrawTicket, + VaultStakerWithdrawTicketEmpty, } diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs new file mode 100644 index 00000000..96ec234e --- /dev/null +++ b/vault_core/src/operator_delegation.rs @@ -0,0 +1,260 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::result::{VaultCoreError, VaultCoreResult}; + +/// Represents an operator that has opted-in to the vault and any associated stake on this operator +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +pub struct OperatorDelegation { + /// The operator pubkey that has opted-in to the vault + operator: Pubkey, + + /// The amount of stake that is currently active on the operator + staked_amount: u64, + + /// Any stake that was deactivated in the current epoch + enqueued_for_cooldown_amount: u64, + + /// Any stake that was deactivated in the previous epoch, + /// to be available for re-delegation in the current epoch + 1 + cooling_down_amount: u64, + + /// Any stake that was enqueued for withdraw in the current epoch. + /// These funds are earmarked for withdraw and can't be redelegated once inactive. + enqueued_for_withdraw_amount: u64, + + /// Any stake that was enqueued for withdraw in the previous epoch, + /// to be available for withdrawal in the current epoch + 1 + cooling_down_for_withdraw_amount: u64, +} + +impl OperatorDelegation { + pub const fn new(operator: Pubkey) -> Self { + Self { + operator, + staked_amount: 0, + enqueued_for_cooldown_amount: 0, + cooling_down_amount: 0, + enqueued_for_withdraw_amount: 0, + cooling_down_for_withdraw_amount: 0, + } + } + + /// # Returns + /// The operator pubkey + pub const fn operator(&self) -> Pubkey { + self.operator + } + + /// # Returns + /// The active amount of stake on the operator + pub const fn staked_amount(&self) -> u64 { + self.staked_amount + } + + /// # Returns + /// The enqueued for cooldown amount of stake on the operator for the last updated epoch + pub const fn enqueued_for_cooldown_amount(&self) -> u64 { + self.enqueued_for_cooldown_amount + } + + /// # Returns + /// The cooling down amount of stake on the operator + pub const fn cooling_down_amount(&self) -> u64 { + self.cooling_down_amount + } + + /// # Returns + /// The enqueued for withdraw amount of stake on the operator for the last updated epoch + pub const fn enqueued_for_withdraw_amount(&self) -> u64 { + self.enqueued_for_withdraw_amount + } + + /// # Returns + /// The cooling down for withdraw amount of stake on the operator + pub const fn cooling_down_for_withdraw_amount(&self) -> u64 { + self.cooling_down_for_withdraw_amount + } + + /// # Returns + /// The total amount of stake on the operator that can be applied for security, which includes + /// the active and any cooling down stake for re-delegation or withdrawal + pub fn delegated_security(&self) -> VaultCoreResult { + self.staked_amount + .checked_add(self.enqueued_for_cooldown_amount) + .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? + .checked_add(self.cooling_down_amount) + .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? + .checked_add(self.enqueued_for_withdraw_amount) + .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? + .checked_add(self.cooling_down_for_withdraw_amount) + .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow) + } + + #[inline(always)] + pub fn update(&mut self) -> u64 { + let available_for_withdraw = self.cooling_down_for_withdraw_amount; + self.cooling_down_amount = self.enqueued_for_cooldown_amount; + self.enqueued_for_cooldown_amount = 0; + self.cooling_down_for_withdraw_amount = self.enqueued_for_withdraw_amount; + self.enqueued_for_withdraw_amount = 0; + + available_for_withdraw + } + + pub fn slash(&mut self, slash_amount: u64) -> VaultCoreResult<()> { + let total_security_amount = self.delegated_security()?; + if slash_amount > total_security_amount { + return Err(VaultCoreError::VaultSlashingUnderflow); + } + + let mut remaining_slash = slash_amount; + + // Helper function to calculate and apply slash based on pro-rata share + let mut apply_slash = |amount: &mut u64| -> VaultCoreResult<()> { + if *amount == 0 || remaining_slash == 0 { + return Ok(()); + } + let pro_rata_slash = ((*amount as u128) * (slash_amount as u128)) + .checked_div(total_security_amount as u128) + .ok_or(VaultCoreError::VaultSlashingDivisionByZero)?; + let actual_slash = std::cmp::min( + pro_rata_slash as u64, + std::cmp::min(*amount, remaining_slash), + ); + *amount = amount + .checked_sub(actual_slash) + .ok_or(VaultCoreError::VaultSlashingUnderflow)?; + remaining_slash = remaining_slash + .checked_sub(actual_slash) + .ok_or(VaultCoreError::VaultSlashingUnderflow)?; + Ok(()) + }; + + // Slash from each bucket + apply_slash(&mut self.staked_amount)?; + apply_slash(&mut self.enqueued_for_cooldown_amount)?; + apply_slash(&mut self.cooling_down_amount)?; + apply_slash(&mut self.enqueued_for_withdraw_amount)?; + apply_slash(&mut self.cooling_down_for_withdraw_amount)?; + + // Ensure we've slashed the exact amount requested + if remaining_slash > 0 { + return Err(VaultCoreError::VaultSlashingIncomplete); + } + + Ok(()) + } + + pub fn undelegate(&mut self, amount: u64) -> VaultCoreResult<()> { + self.staked_amount = self + .staked_amount + .checked_sub(amount) + .ok_or(VaultCoreError::VaultDelegationUnderflow)?; + self.enqueued_for_cooldown_amount = self + .enqueued_for_cooldown_amount + .checked_add(amount) + .ok_or(VaultCoreError::VaultDelegationOverflow)?; + + Ok(()) + } + + pub fn undelegate_for_withdraw(&mut self, amount: u64) -> VaultCoreResult<()> { + self.staked_amount = self + .staked_amount + .checked_sub(amount) + .ok_or(VaultCoreError::VaultDelegationUnderflow)?; + self.enqueued_for_withdraw_amount = self + .enqueued_for_withdraw_amount + .checked_add(amount) + .ok_or(VaultCoreError::VaultDelegationOverflow)?; + + Ok(()) + } + + pub fn delegate(&mut self, amount: u64) -> VaultCoreResult<()> { + self.staked_amount = self + .staked_amount + .checked_add(amount) + .ok_or(VaultCoreError::VaultDelegationOverflow)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use solana_program::pubkey::Pubkey; + + use crate::operator_delegation::OperatorDelegation; + + #[test] + fn test_delegate() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100).unwrap(); + assert_eq!(operator_delegation.staked_amount(), 100); + assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + } + + #[test] + fn test_undelegate_with_updates() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100).unwrap(); + operator_delegation.undelegate(50).unwrap(); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 50); + assert_eq!(operator_delegation.cooling_down_amount(), 0); + assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + + assert_eq!(operator_delegation.update(), 0); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 0); + assert_eq!(operator_delegation.cooling_down_amount(), 50); + assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + + assert_eq!(operator_delegation.update(), 0); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 0); + assert_eq!(operator_delegation.cooling_down_amount(), 0); + assert_eq!(operator_delegation.delegated_security().unwrap(), 50); + } + + #[test] + fn test_undelegate_for_withdraw_with_updates() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100).unwrap(); + operator_delegation.undelegate_for_withdraw(50).unwrap(); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 50); + assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 0); + assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + + assert_eq!(operator_delegation.update(), 0); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 0); + assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 50); + assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + + assert_eq!(operator_delegation.update(), 50); + + assert_eq!(operator_delegation.staked_amount(), 50); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 0); + assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 0); + assert_eq!(operator_delegation.delegated_security().unwrap(), 50); + } + + #[test] + fn test_slashing_simple() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100_000).unwrap(); + operator_delegation.undelegate(10_000).unwrap(); + operator_delegation.slash(5_000).unwrap(); + + assert_eq!(operator_delegation.delegated_security().unwrap(), 95_000); + // assert_eq!(operator_delegation.staked_amount(), 85_000); + } +} diff --git a/vault_core/src/result.rs b/vault_core/src/result.rs index 90197f3c..13ba5db2 100644 --- a/vault_core/src/result.rs +++ b/vault_core/src/result.rs @@ -89,6 +89,32 @@ pub enum VaultCoreError { VaultAvsSlasherOperatorNotWritable, VaultAvsSlasherOperatorOverflow, VaultAvsSlasherOperatorMaxSlashableExceeded, + VaultStakerWithdrawTicketEmpty, + VaultStakerWithdrawTicketEmptyInvalidOwner, + VaultStakerWithdrawTicketEmptyInvalidData(String), + VaultStakerWithdrawTicketEmptyInvalidAccountType, + VaultStakerWithdrawTicketEmptyInvalidPda, + VaultStakerWithdrawTicketInvalidProgramOwner, + VaultStakerWithdrawTicketNotWritable, + VaultOperatorActiveStakeOverflow, + VaultDelegationListUpdateOverflow, + VaultDelegationListTotalDelegationOverflow, + VaultDelegationUnderflow, + VaultDelegationOverflow, + VaultSlashingDivisionByZero, + VaultSlashingIncomplete, + VaultWithdrawOverflow, + VaultEmpty, + VaultInsufficientFunds, + VaultDelegationListInsufficientSecurity, + WithdrawAmountExceedsDelegatedFunds, + ArithmeticOverflow, + ArithmeticUnderflow, + UndelegationIncomplete, + VaultDelegationListAmountWithdrawableUnderflow, + VaultDelegationListUpdateRequired, + VaultStakerWithdrawTicketOverflow, + VaultStakerWithdrawTicketNotWithdrawable, } impl From for ProgramError { diff --git a/vault_core/src/vault.rs b/vault_core/src/vault.rs index fc47e5c5..ca0a8ba8 100644 --- a/vault_core/src/vault.rs +++ b/vault_core/src/vault.rs @@ -187,18 +187,36 @@ impl Vault { self.tokens_deposited = tokens_deposited; } + pub fn calculate_assets_returned_amount(&self, lrt_amount: u64) -> VaultCoreResult { + if self.lrt_supply == 0 { + return Err(VaultCoreError::VaultEmpty); + } else if lrt_amount > self.lrt_supply { + return Err(VaultCoreError::VaultInsufficientFunds); + } + + lrt_amount + .checked_mul(self.tokens_deposited) + .ok_or(VaultCoreError::VaultWithdrawOverflow)? + .checked_div(self.lrt_supply) + .ok_or(VaultCoreError::VaultWithdrawOverflow) + } + + pub fn calculate_lrt_mint_amount(&self, amount: u64) -> VaultCoreResult { + if self.tokens_deposited == 0 { + return Ok(amount); + } + + amount + .checked_mul(self.lrt_supply) + .ok_or(VaultCoreError::VaultDepositOverflow)? + .checked_div(self.tokens_deposited) + .ok_or(VaultCoreError::VaultDepositOverflow) + } + /// Deposit tokens into the vault pub fn deposit_and_mint_with_capacity_check(&mut self, amount: u64) -> VaultCoreResult { // the number of tokens to mint is the pro-rata amount of the total tokens deposited and the LRT supply - let num_tokens_to_mint = if self.tokens_deposited == 0 { - amount - } else { - amount - .checked_mul(self.lrt_supply) - .ok_or(VaultCoreError::VaultDepositOverflow)? - .checked_div(self.tokens_deposited) - .ok_or(VaultCoreError::VaultDepositOverflow)? - }; + let num_tokens_to_mint = self.calculate_lrt_mint_amount(amount)?; // deposit tokens + check against capacity let total_post_deposit = self @@ -246,11 +264,6 @@ impl Vault { self.lrt_supply = lrt_supply; } - pub fn increment_lrt_supply(&mut self, amount: u64) -> Option { - self.lrt_supply = self.lrt_supply.checked_add(amount)?; - Some(self.lrt_supply) - } - pub const fn lrt_supply(&self) -> u64 { self.lrt_supply } @@ -372,7 +385,6 @@ impl Vault { return Err(VaultCoreError::VaultInvalidProgramOwner); } - // The AvsState shall be properly deserialized and valid struct let state = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| VaultCoreError::VaultInvalidData(e.to_string()))?; if !state.is_struct_valid() { @@ -520,4 +532,57 @@ mod tests { Err(VaultCoreError::VaultDepositExceedsCapacity) ); } + + #[test] + fn test_calculate_assets_returned_amount_ok() { + let mut vault = Vault::new( + Pubkey::new_unique(), + Pubkey::new_unique(), + Pubkey::new_unique(), + 0, + Pubkey::new_unique(), + 0, + 0, + 0, + ); + + vault.set_lrt_supply(100_000); + vault.set_tokens_deposited(100_000); + assert_eq!( + vault.calculate_assets_returned_amount(50_000).unwrap(), + 50_000 + ); + + vault.set_tokens_deposited(90_000); + vault.set_lrt_supply(100_000); + assert_eq!( + vault.calculate_assets_returned_amount(50_000).unwrap(), + 45_000 + ); + + vault.set_tokens_deposited(110_000); + vault.set_lrt_supply(100_000); + assert_eq!( + vault.calculate_assets_returned_amount(50_000).unwrap(), + 55_000 + ); + + vault.set_tokens_deposited(100); + vault.set_lrt_supply(0); + assert_eq!( + vault.calculate_assets_returned_amount(100), + Err(VaultCoreError::VaultEmpty) + ); + + vault.set_tokens_deposited(100); + vault.set_lrt_supply(1); + assert_eq!( + vault.calculate_assets_returned_amount(100), + Err(VaultCoreError::VaultInsufficientFunds) + ); + + vault.set_tokens_deposited(100); + vault.set_lrt_supply(13); + assert_eq!(vault.calculate_assets_returned_amount(1).unwrap(), 7); + } } diff --git a/vault_core/src/vault_avs_slasher_operator_ticket.rs b/vault_core/src/vault_avs_slasher_operator_ticket.rs index 4f8d1872..645acafd 100644 --- a/vault_core/src/vault_avs_slasher_operator_ticket.rs +++ b/vault_core/src/vault_avs_slasher_operator_ticket.rs @@ -159,7 +159,6 @@ impl VaultAvsSlasherOperatorTicket { return Err(VaultCoreError::VaultAvsSlasherOperatorInvalidOwner); } - // The AvsState shall be properly deserialized and valid struct let vault_avs_slasher_operator_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()) .map_err(|e| VaultCoreError::VaultAvsSlasherOperatorInvalidData(e.to_string()))?; diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 45834e1e..b14f7d56 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -1,54 +1,19 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use jito_restaking_sanitization::{assert_with_msg, realloc}; +use jito_restaking_sanitization::realloc; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, rent::Rent, }; use crate::{ + operator_delegation::OperatorDelegation, result::{VaultCoreError, VaultCoreResult}, AccountType, }; -/// Represents an operator that has opted-in to the vault and any associated stake on this operator -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] -pub struct OperatorDelegation { - /// The operator pubkey that has opted-in to the vault - operator: Pubkey, - - /// The amount of stake that is currently active on the operator - active_amount: u64, - - /// The amount of stake that is currently cooling down on the operator - cooling_down_amount: u64, -} - -impl OperatorDelegation { - pub const fn new(operator: Pubkey) -> Self { - Self { - operator, - active_amount: 0, - cooling_down_amount: 0, - } - } - - /// # Returns - /// The operator pubkey - pub const fn operator(&self) -> Pubkey { - self.operator - } - - /// # Returns - /// The active amount of stake on the operator - pub const fn active_amount(&self) -> u64 { - self.active_amount - } - - /// # Returns - /// The cooling down amount of stake on the operator - pub const fn cooling_down_amount(&self) -> u64 { - self.cooling_down_amount - } +pub enum UndelegateForWithdrawMethod { + /// Withdraws from each operator's delegated amount in proportion to the total delegated amount + ProRata, } /// Represents the operators which have opted-in to this vault @@ -63,6 +28,10 @@ pub struct VaultDelegationList { /// the list of delegations delegations: Vec, + /// The amount withdrawable by valid tickets at any given point in time. This amount is set aside + /// and can't be delegated because there's a withdraw ticket with a claim on it. + amount_withdrawable_by_tickets: u64, + /// The last slot the operator list was updated. /// Delegation information here is out of date if the last update epoch < current epoch last_slot_updated: u64, @@ -80,6 +49,7 @@ impl VaultDelegationList { account_type: AccountType::VaultDelegationList, vault, delegations: vec![], + amount_withdrawable_by_tickets: 0, last_slot_updated: 0, reserved: [0; 128], bump, @@ -93,29 +63,86 @@ impl VaultDelegationList { } /// # Returns - /// The list of operators that have opted-in to this vault + /// The list of delegations pub fn delegations(&self) -> &[OperatorDelegation] { &self.delegations } - pub fn needs_update(&self, slot: u64, epoch_length: u64) -> bool { - self.last_slot_updated.checked_div(epoch_length).unwrap() - < slot.checked_div(epoch_length).unwrap() + pub const fn amount_withdrawable_by_tickets(&self) -> u64 { + self.amount_withdrawable_by_tickets + } + + pub fn decrement_amount_withdrawable_by_tickets(&mut self, amount: u64) -> VaultCoreResult<()> { + self.amount_withdrawable_by_tickets = self + .amount_withdrawable_by_tickets + .checked_sub(amount) + .ok_or(VaultCoreError::VaultDelegationListAmountWithdrawableUnderflow)?; + Ok(()) + } + + /// Returns the total active + cooling down delegations + pub fn delegated_security(&self) -> VaultCoreResult { + let mut total: u64 = 0; + for operator in self.delegations.iter() { + total = total + .checked_add(operator.delegated_security()?) + .ok_or(VaultCoreError::VaultDelegationListTotalDelegationOverflow)?; + } + Ok(total) + } + + #[inline(always)] + pub fn is_update_needed(&self, slot: u64, epoch_length: u64) -> bool { + let last_updated_epoch = self.last_slot_updated.checked_div(epoch_length).unwrap(); + let current_epoch = slot.checked_div(epoch_length).unwrap(); + last_updated_epoch < current_epoch } - pub fn update_delegations(&mut self, slot: u64, epoch_length: u64) -> bool { + #[inline(always)] + pub fn check_update_needed(&self, slot: u64, epoch_length: u64) -> VaultCoreResult<()> { + if self.is_update_needed(slot, epoch_length) { + Err(VaultCoreError::VaultDelegationListUpdateRequired) + } else { + Ok(()) + } + } + + /// Updates the delegation list for the current epoch if needed. + #[inline(always)] + pub fn update(&mut self, slot: u64, epoch_length: u64) -> VaultCoreResult { let last_epoch_update = self.last_slot_updated.checked_div(epoch_length).unwrap(); let current_epoch = slot.checked_div(epoch_length).unwrap(); - if last_epoch_update < current_epoch { - for operator in self.delegations.iter_mut() { - operator.cooling_down_amount = 0; + // time should only move forward, unwrap is safe + let epoch_diff = current_epoch.checked_sub(last_epoch_update).unwrap(); + match epoch_diff { + 0 => return Ok(false), + 1 => { + // enqueued -> cooling down, enqueued wiped + for operator in self.delegations.iter_mut() { + self.amount_withdrawable_by_tickets = self + .amount_withdrawable_by_tickets + .checked_add(operator.update()) + .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; + } + } + _ => { + // max updates required are two (enqueued -> cooling down -> wiped out), + // so this only needs to be done twice even if >2 epochs have passed + for operator in self.delegations.iter_mut() { + let amount_withdrawal_1 = operator.update(); + let amount_withdrawal_2 = operator.update(); + + self.amount_withdrawable_by_tickets = self + .amount_withdrawable_by_tickets + .checked_add(amount_withdrawal_1) + .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)? + .checked_add(amount_withdrawal_2) + .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; + } } - self.last_slot_updated = slot; - true - } else { - false } + Ok(true) } /// Delegates an amount of stake to an operator and ensures the amount delegated doesn't @@ -133,37 +160,90 @@ impl VaultDelegationList { operator: Pubkey, amount: u64, total_deposited: u64, - ) -> Result<(), ProgramError> { - let total_delegations = self.total_delegation(); - assert_with_msg( - total_delegations.is_some(), - ProgramError::InvalidArgument, - "Total delegation overflow", - )?; - - // ensure the new deposit doesn't overallocate the vault - let delegated_after_deposit = total_delegations.unwrap().checked_add(amount); - assert_with_msg( - delegated_after_deposit.is_some(), - ProgramError::InvalidArgument, - "Total delegation overflow", - )?; - assert_with_msg( - delegated_after_deposit.unwrap() <= total_deposited, - ProgramError::InvalidArgument, - "overdelegated amount", - )?; - - if let Some(operator) = self.delegations.iter_mut().find(|d| d.operator == operator) { - operator.active_amount = - operator.active_amount.checked_add(amount).ok_or_else(|| { - msg!("Delegation overflow"); - ProgramError::InvalidArgument - })?; + ) -> VaultCoreResult<()> { + let delegated_security = self.delegated_security()?; + + // Ensure the amount delegated doesn't exceed the total deposited + let security_available_for_delegation = total_deposited + .checked_sub(delegated_security) + .and_then(|assets_available_to_delegate| { + assets_available_to_delegate.checked_sub(self.amount_withdrawable_by_tickets) + }) + .ok_or(VaultCoreError::VaultDelegationListInsufficientSecurity)?; + + if amount > security_available_for_delegation { + msg!("Insufficient security available for delegation"); + return Err(VaultCoreError::VaultDelegationListInsufficientSecurity); + } + + if let Some(operator) = self + .delegations + .iter_mut() + .find(|d| d.operator() == operator) + { + operator.delegate(amount)?; } else { - let mut operator = OperatorDelegation::new(operator); - operator.active_amount = amount; - self.delegations.push(operator); + let mut delegation = OperatorDelegation::new(operator); + delegation.delegate(amount)?; + self.delegations.push(delegation); + } + + Ok(()) + } + + /// Undelegates an amount of stake from the vault for withdrawal + pub fn undelegate_for_withdraw( + &mut self, + amount: u64, + method: UndelegateForWithdrawMethod, + ) -> VaultCoreResult<()> { + match method { + UndelegateForWithdrawMethod::ProRata => self.undelegate_for_withdraw_pro_rata(amount), + } + } + + /// TODO (LB): what happens if try to undelegate more than total delegated? Must mean some are set + /// aside that haven't been delegated yet? + fn undelegate_for_withdraw_pro_rata(&mut self, amount: u64) -> VaultCoreResult<()> { + let total_delegated = self.delegated_security()?; + + // TODO (LB): what if want to withdraw > than the total delegated amount? + // one could set aside undelegated assets into the withdraw bucket? + if amount > total_delegated || total_delegated == 0 { + return Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds); + } + + let mut remaining_to_undelegate = amount; + + for delegation in self.delegations.iter_mut() { + let delegated_security = delegation.delegated_security()?; + let undelegate_amount = (delegated_security as u128) + .checked_mul(amount as u128) + .and_then(|product| product.checked_div(total_delegated as u128)) + .and_then(|result| result.try_into().ok()) + .ok_or(VaultCoreError::ArithmeticOverflow)?; + + if undelegate_amount > 0 { + delegation.undelegate_for_withdraw(undelegate_amount)?; + remaining_to_undelegate = remaining_to_undelegate + .checked_sub(undelegate_amount) + .ok_or(VaultCoreError::ArithmeticUnderflow)?; + } + } + + // Handle any remaining dust due to rounding + if remaining_to_undelegate > 0 { + for delegation in self.delegations.iter_mut() { + if delegation.staked_amount() >= remaining_to_undelegate { + delegation.undelegate_for_withdraw(remaining_to_undelegate)?; + remaining_to_undelegate = 0; + break; + } + } + } + + if remaining_to_undelegate > 0 { + return Err(VaultCoreError::UndelegationIncomplete); } Ok(()) @@ -175,19 +255,12 @@ impl VaultDelegationList { /// * `operator` - The operator pubkey to undelegate from /// * `amount` - The amount of stake to undelegate pub fn undelegate(&mut self, operator: Pubkey, amount: u64) -> Result<(), ProgramError> { - if let Some(operator) = self.delegations.iter_mut().find(|d| d.operator == operator) { - operator.active_amount = - operator.active_amount.checked_sub(amount).ok_or_else(|| { - msg!("Delegation underflow"); - ProgramError::InvalidArgument - })?; - operator.cooling_down_amount = operator - .cooling_down_amount - .checked_add(amount) - .ok_or_else(|| { - msg!("Delegation overflow"); - ProgramError::InvalidArgument - })?; + if let Some(operator) = self + .delegations + .iter_mut() + .find(|d| d.operator() == operator) + { + operator.undelegate(amount)?; } else { msg!("Delegation not found"); return Err(ProgramError::InvalidArgument); @@ -197,65 +270,15 @@ impl VaultDelegationList { } pub fn slash(&mut self, operator: &Pubkey, slash_amount: u64) -> VaultCoreResult<()> { - if let Some(operator) = self - .delegations + self.delegations .iter_mut() - .find(|x| x.operator == *operator) - { - let total_staked_amount = operator - .active_amount - .checked_add(operator.cooling_down_amount) - .ok_or(VaultCoreError::VaultSlashingOverflow)?; - - let active_slash_amount = operator - .active_amount - .checked_mul(slash_amount) - .ok_or(VaultCoreError::VaultSlashingOverflow)? - .checked_div(total_staked_amount) - .unwrap_or(0); - - msg!( - "slashing {} from active, {} from cooling down", - active_slash_amount, - slash_amount - .checked_sub(active_slash_amount) - .unwrap_or(99999999999999) - ); - let cooling_down_slash_amount = slash_amount - .checked_sub(active_slash_amount) - .ok_or(VaultCoreError::VaultSlashingUnderflow)?; - - operator.active_amount = operator - .active_amount - .checked_sub(active_slash_amount) - .ok_or(VaultCoreError::VaultSlashingUnderflow)?; - operator.cooling_down_amount = operator - .cooling_down_amount - .checked_sub(cooling_down_slash_amount) - .ok_or(VaultCoreError::VaultSlashingUnderflow)?; - - Ok(()) - } else { - Err(VaultCoreError::VaultOperatorNotFound) - } - } - - /// Returns the total active + cooling down delegations - pub fn total_delegation(&self) -> Option { - let mut total: u64 = 0; - for operator in self.delegations.iter() { - total = total - .checked_add(operator.active_amount)? - .checked_add(operator.cooling_down_amount)?; - } - Some(total) + .find(|x| x.operator() == *operator) + .ok_or(VaultCoreError::VaultOperatorNotFound)? + .slash(slash_amount) } pub fn seeds(vault: &Pubkey) -> Vec> { - vec![ - b"vault_supported_operators".to_vec(), - vault.to_bytes().to_vec(), - ] + vec![b"vault_delegation_list".to_vec(), vault.to_bytes().to_vec()] } pub fn find_program_address(program_id: &Pubkey, vault: &Pubkey) -> (Pubkey, u8, Vec>) { @@ -353,3 +376,241 @@ impl<'a, 'info> SanitizedVaultDelegationList<'a, 'info> { Ok(()) } } + +#[cfg(test)] +mod tests { + use solana_program::pubkey::Pubkey; + + use super::*; + + fn setup_vault_delegation_list() -> VaultDelegationList { + VaultDelegationList::new(Pubkey::new_unique(), 255) + } + + #[test] + fn test_delegate_new_operator() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + + assert!(list.delegate(operator, 100, 1_000).is_ok()); + assert_eq!(list.delegated_security().unwrap(), 100); + + assert_eq!(list.delegations().len(), 1); + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.operator(), operator); + assert_eq!(delegation.staked_amount(), 100); + assert_eq!(delegation.delegated_security().unwrap(), 100); + } + + #[test] + fn test_delegate_existing_operator() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + list.delegate(operator, 100, 1_000).unwrap(); + list.delegate(operator, 50, 1_000).unwrap(); + assert_eq!(list.delegated_security().unwrap(), 150); + + assert_eq!(list.delegations().len(), 1); + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.operator(), operator); + assert_eq!(delegation.staked_amount(), 150); + } + + #[test] + fn test_undelegate() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + + list.delegate(operator, 100, 1_000).unwrap(); + list.undelegate(operator, 30).unwrap(); + + assert_eq!(list.delegations().len(), 1); + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 70); + assert_eq!(delegation.enqueued_for_cooldown_amount(), 30); + assert_eq!(delegation.delegated_security().unwrap(), 100); + } + + #[test] + fn test_slash() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + + list.delegate(operator, 100, 1_000).unwrap(); + list.slash(&operator, 20).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 80); + assert_eq!(delegation.delegated_security().unwrap(), 80); + } + + #[test] + fn test_update() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + let amount = 100; + let total_deposited = 1000; + let epoch_length = 100; + + list.delegate(operator, amount, total_deposited).unwrap(); + list.undelegate(operator, 30).unwrap(); + + // Simulate passing of one epoch + assert!(list.update(epoch_length, epoch_length).unwrap()); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 70); + assert_eq!(delegation.cooling_down_amount(), 30); + assert_eq!(delegation.enqueued_for_cooldown_amount(), 0); + + // Simulate passing of another epoch + assert!(list.update(epoch_length * 2, epoch_length).unwrap()); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 70); + assert_eq!(delegation.cooling_down_amount(), 0); + assert_eq!(list.amount_withdrawable_by_tickets(), 0); + } + + #[test] + fn test_undelegate_for_withdraw_and_over_delegate() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + let total_deposited = 1000; + let initial_delegation = 500; + let undelegate_amount = 200; + let over_delegation_attempt = 600; + + list.delegate(operator, initial_delegation, total_deposited) + .unwrap(); + list.undelegate_for_withdraw(undelegate_amount, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + assert_eq!(list.delegations().len(), 1); + let delegation = list.delegations().get(0).unwrap(); + assert_eq!( + delegation.staked_amount(), + initial_delegation - undelegate_amount + ); + assert_eq!(delegation.enqueued_for_withdraw_amount(), undelegate_amount); + assert_eq!(list.amount_withdrawable_by_tickets(), 0); + + assert!(list.update(100, 100).unwrap()); + assert!(list.update(200, 100).unwrap()); + assert_eq!(list.amount_withdrawable_by_tickets(), undelegate_amount); + assert_eq!( + list.delegated_security().unwrap(), + initial_delegation - undelegate_amount + ); + + // 1000 total deposits, 300 delegated, 200 enqueued for withdraw + // if try to delegate 600, should fail because some assets are set aside for withdraw + assert_eq!( + list.delegate(operator, over_delegation_attempt, total_deposited), + Err(VaultCoreError::VaultDelegationListInsufficientSecurity) + ); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_single_operator() { + let mut list = VaultDelegationList::new(Pubkey::new_unique(), 255); + let operator = Pubkey::new_unique(); + list.delegate(operator, 1000, 1000).unwrap(); + + list.undelegate_for_withdraw(500, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 500); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 500); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_multiple_operators() { + let mut list = setup_vault_delegation_list(); + let operator1 = Pubkey::new_unique(); + let operator2 = Pubkey::new_unique(); + let operator3 = Pubkey::new_unique(); + + list.delegate(operator1, 1000, 3000).unwrap(); + list.delegate(operator2, 1500, 3000).unwrap(); + list.delegate(operator3, 500, 3000).unwrap(); + + let total_delegated_before_undelegation = list.delegated_security().unwrap(); + + list.undelegate_for_withdraw(600, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + assert_eq!( + total_delegated_before_undelegation, + list.delegated_security().unwrap() + ); + + // 3000 total staked, 600 withdrawn + let delegations = list.delegations(); + assert_eq!(delegations[0].staked_amount(), 800); + assert_eq!(delegations[0].enqueued_for_withdraw_amount(), 200); + assert_eq!(delegations[1].staked_amount(), 1200); + assert_eq!(delegations[1].enqueued_for_withdraw_amount(), 300); + assert_eq!(delegations[2].staked_amount(), 400); + assert_eq!(delegations[2].enqueued_for_withdraw_amount(), 100); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_rounding() { + let mut list = setup_vault_delegation_list(); + let operator1 = Pubkey::new_unique(); + let operator2 = Pubkey::new_unique(); + let operator3 = Pubkey::new_unique(); + + list.delegate(operator1, 100, 301).unwrap(); + list.delegate(operator2, 100, 301).unwrap(); + list.delegate(operator3, 101, 301).unwrap(); + + list.undelegate_for_withdraw(100, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + let delegations = list.delegations(); + assert_eq!(delegations[0].enqueued_for_withdraw_amount(), 34); + assert_eq!(delegations[1].enqueued_for_withdraw_amount(), 33); + assert_eq!(delegations[2].enqueued_for_withdraw_amount(), 33); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_insufficient_funds() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + list.delegate(operator, 100, 100).unwrap(); + + let result = list.undelegate_for_withdraw(101, UndelegateForWithdrawMethod::ProRata); + assert!(matches!( + result, + Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds) + )); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_no_delegations() { + let mut list = setup_vault_delegation_list(); + + let result = list.undelegate_for_withdraw(100, UndelegateForWithdrawMethod::ProRata); + assert!(matches!( + result, + Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds) + )); + } + + #[test] + fn test_undelegate_for_withdraw_pro_rata_zero_amount() { + let mut list = setup_vault_delegation_list(); + let operator = Pubkey::new_unique(); + list.delegate(operator, 100, 100).unwrap(); + + list.undelegate_for_withdraw(0, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 100); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 0); + } +} diff --git a/vault_core/src/vault_staker_withdraw_ticket.rs b/vault_core/src/vault_staker_withdraw_ticket.rs new file mode 100644 index 00000000..ac13c2e3 --- /dev/null +++ b/vault_core/src/vault_staker_withdraw_ticket.rs @@ -0,0 +1,197 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; + +use crate::{ + result::{VaultCoreError, VaultCoreResult}, + AccountType, +}; + +/// Represents a pending withdrawal from a vault by a staker. +/// For every withdraw ticket, there's an associated token account owned by the withdraw ticket +/// with the staker's LRT. +#[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] +#[repr(C)] +pub struct VaultStakerWithdrawTicket { + /// The account type + account_type: AccountType, + + /// The vault being withdrawn from + vault: Pubkey, + + /// The staker withdrawing from the vault + staker: Pubkey, + + /// The base account used as a PDA seed + base: Pubkey, + + /// The amount of assets allocated for this staker's withdraw + withdraw_allocation_amount: u64, + + /// The amount of LRT held in the VaultStakerWithdrawTicket token account at the time of creation + /// At first glance, this seems redundant, but it's necessary to prevent someone from depositing + /// more LRT into the token account and skipping the withdraw queue. + lrt_amount: u64, + + /// The slot the withdrawal was enqueued + slot_unstaked: u64, + + /// The bump seed used to create the PDA + bump: u8, +} + +impl VaultStakerWithdrawTicket { + pub const fn new( + vault: Pubkey, + staker: Pubkey, + base: Pubkey, + withdraw_allocation_amount: u64, + lrt_amount: u64, + slot_unstaked: u64, + bump: u8, + ) -> Self { + Self { + account_type: AccountType::VaultStakerWithdrawTicket, + vault, + staker, + base, + withdraw_allocation_amount, + lrt_amount, + slot_unstaked, + bump, + } + } + + pub const fn vault(&self) -> Pubkey { + self.vault + } + + pub const fn staker(&self) -> Pubkey { + self.staker + } + + pub const fn base(&self) -> Pubkey { + self.base + } + + pub const fn slot_unstaked(&self) -> u64 { + self.slot_unstaked + } + + pub const fn withdraw_allocation_amount(&self) -> u64 { + self.withdraw_allocation_amount + } + + pub const fn lrt_amount(&self) -> u64 { + self.lrt_amount + } + + /// In order for the ticket to be withdrawable, it needs to be more than one **full** epoch + /// since unstaking + #[inline(always)] + pub fn check_withdrawable(&self, slot: u64, epoch_length: u64) -> VaultCoreResult<()> { + let current_epoch = slot.checked_div(epoch_length).unwrap(); // epoch_length is always > 0 + let epoch_unstaked = self.slot_unstaked.checked_div(epoch_length).unwrap(); + if epoch_unstaked + .checked_add(1) + .ok_or(VaultCoreError::VaultStakerWithdrawTicketOverflow)? + < current_epoch + { + return Err(VaultCoreError::VaultStakerWithdrawTicketNotWithdrawable); + } + Ok(()) + } + + pub fn seeds(vault: &Pubkey, staker: &Pubkey, base: &Pubkey) -> Vec> { + Vec::from_iter([ + b"vault_staker_withdraw_ticket".to_vec(), + vault.to_bytes().to_vec(), + staker.to_bytes().to_vec(), + base.to_bytes().to_vec(), + ]) + } + + pub fn find_program_address( + program_id: &Pubkey, + vault: &Pubkey, + staker: &Pubkey, + base: &Pubkey, + ) -> (Pubkey, u8, Vec>) { + let seeds = Self::seeds(vault, staker, base); + 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 deserialize_checked( + program_id: &Pubkey, + account: &AccountInfo, + vault: &Pubkey, + staker: &Pubkey, + ) -> VaultCoreResult { + if account.data_is_empty() { + return Err(VaultCoreError::VaultStakerWithdrawTicketEmpty); + } + if account.owner != program_id { + return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidOwner); + } + + let vault_staker_withdraw_ticket = + Self::deserialize(&mut account.data.borrow_mut().as_ref()).map_err(|e| { + VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidData(e.to_string()) + })?; + if vault_staker_withdraw_ticket.account_type != AccountType::VaultStakerWithdrawTicketEmpty + { + return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidAccountType); + } + + let mut seeds = Self::seeds(vault, staker, &vault_staker_withdraw_ticket.base()); + seeds.push(vec![vault_staker_withdraw_ticket.bump]); + let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_ref()).collect(); + let expected_pubkey = Pubkey::create_program_address(&seeds_iter, program_id) + .map_err(|_| VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidPda)?; + if expected_pubkey != *account.key { + return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidPda); + } + + Ok(vault_staker_withdraw_ticket) + } +} + +pub struct SanitizedVaultStakerWithdrawTicket<'a, 'info> { + account: &'a AccountInfo<'info>, + vault_staker_withdraw_ticket: VaultStakerWithdrawTicket, +} + +impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { + pub fn sanitize( + program_id: &Pubkey, + account: &'a AccountInfo<'info>, + vault: &Pubkey, + staker: &Pubkey, + expected_writable: bool, + ) -> VaultCoreResult { + if expected_writable && !account.is_writable { + return Err(VaultCoreError::VaultStakerWithdrawTicketNotWritable); + } + + let vault_staker_withdraw_ticket = + VaultStakerWithdrawTicket::deserialize_checked(program_id, account, vault, staker)?; + + Ok(SanitizedVaultStakerWithdrawTicket { + account, + vault_staker_withdraw_ticket, + }) + } + + pub const fn vault_staker_withdraw_ticket(&self) -> &VaultStakerWithdrawTicket { + &self.vault_staker_withdraw_ticket + } + + pub fn vault_staker_withdraw_ticket_mut(&mut self) -> &mut VaultStakerWithdrawTicket { + &mut self.vault_staker_withdraw_ticket + } + + pub const fn account(&self) -> &AccountInfo<'info> { + self.account + } +} diff --git a/vault_program/src/add_delegation.rs b/vault_program/src/add_delegation.rs index 2096163b..20e4bc53 100644 --- a/vault_program/src/add_delegation.rs +++ b/vault_program/src/add_delegation.rs @@ -31,19 +31,20 @@ pub fn process_add_delegation( payer, } = SanitizedAccounts::sanitize(program_id, accounts)?; + let slot = Clock::get()?.slot; + let epoch_length = config.config().epoch_length(); + vault .vault() .check_delegation_admin(delegation_admin.account().key)?; - let slot = Clock::get()?.slot; - vault_operator_ticket .vault_operator_ticket() .check_active(slot)?; - vault_delegation_list .vault_delegation_list_mut() - .update_delegations(slot, config.config().epoch_length()); + .check_update_needed(slot, epoch_length)?; + vault_delegation_list.vault_delegation_list_mut().delegate( *operator.key, amount, diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs new file mode 100644 index 00000000..fafb779a --- /dev/null +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -0,0 +1,370 @@ +use jito_restaking_sanitization::{ + assert_with_msg, associated_token_account::SanitizedAssociatedTokenAccount, + close_program_account, signer::SanitizedSignerAccount, system_program::SanitizedSystemProgram, + token_mint::SanitizedTokenMint, token_program::SanitizedTokenProgram, +}; +use jito_vault_core::{ + config::SanitizedConfig, + vault::{SanitizedVault, Vault}, + vault_delegation_list::SanitizedVaultDelegationList, + vault_staker_withdraw_ticket::{SanitizedVaultStakerWithdrawTicket, VaultStakerWithdrawTicket}, +}; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + clock::Clock, + entrypoint::ProgramResult, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, + sysvar::Sysvar, +}; +use spl_token::instruction::{burn, close_account, transfer}; + +pub fn process_burn_withdraw_ticket( + program_id: &Pubkey, + accounts: &[AccountInfo], +) -> ProgramResult { + let SanitizedAccounts { + config, + mut vault, + mut vault_delegation_list, + mut vault_token_account, + mut lrt_mint, + staker, + mut staker_token_account, + staker_lrt_token_account, + vault_staker_withdraw_ticket, + mut vault_staker_withdraw_ticket_token_account, + token_program: _, + system_program: _, + } = SanitizedAccounts::sanitize(program_id, accounts)?; + + let slot = Clock::get()?.slot; + let epoch_length = config.config().epoch_length(); + + assert_with_msg( + vault.vault().lrt_mint() == *lrt_mint.account().key, + ProgramError::InvalidArgument, + "LRT mint mismatch", + )?; + vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .check_withdrawable(slot, epoch_length)?; + vault_delegation_list + .vault_delegation_list_mut() + .check_update_needed(slot, epoch_length)?; + + // find the current redemption amount and the original redemption amount in the withdraw ticket + let redemption_amount = vault.vault().calculate_assets_returned_amount( + vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .lrt_amount(), + )?; + let original_redemption_amount = vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .withdraw_allocation_amount(); + + let actual_withdraw_amount = if redemption_amount > original_redemption_amount { + // The program can guarantee the original redemption amount, but if the redemption amount + // is greater than the original amount, there were rewards that accrued + // to the LRT. + // The program attempts to figure out how much more of the asset can be unstaked to fulfill + // as much of the redemption amount as possible. + // Available unstaked assets is equal to: + // the amount of tokens deposited - any delegated security - the amount reserved for withdraw tickets + let tokens_deposited_in_vault = vault.vault().tokens_deposited(); + let delegated_security_in_vault = vault_delegation_list + .vault_delegation_list() + .delegated_security()?; + let assets_reserved_for_withdraw_tickets = vault_delegation_list + .vault_delegation_list() + .amount_withdrawable_by_tickets(); + + let available_unstaked_assets = tokens_deposited_in_vault + .checked_sub(delegated_security_in_vault) + .ok_or(ProgramError::InsufficientFunds)? + .checked_sub(assets_reserved_for_withdraw_tickets) + .ok_or(ProgramError::InsufficientFunds)?; + + // Calculate the extra amount that can be withdrawn + let extra_amount = redemption_amount + .checked_sub(original_redemption_amount) + .ok_or(ProgramError::ArithmeticOverflow)?; + + // Determine the actual amount to withdraw + let actual_withdrawal_amount = original_redemption_amount + .checked_add(extra_amount.min(available_unstaked_assets)) + .ok_or(ProgramError::ArithmeticOverflow)?; + actual_withdrawal_amount + } else { + redemption_amount + }; + + let lrt_to_burn = vault + .vault() + .calculate_lrt_mint_amount(actual_withdraw_amount)?; + let lrt_amount_to_burn = std::cmp::min( + lrt_to_burn, + vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .lrt_amount(), + ); + + _burn_lrt( + program_id, + &vault, + &staker, + &vault_staker_withdraw_ticket, + &vault_staker_withdraw_ticket_token_account, + &lrt_mint, + lrt_amount_to_burn, + )?; + lrt_mint.reload()?; + vault_staker_withdraw_ticket_token_account.reload()?; + + _transfer_vault_tokens_to_staker( + program_id, + &vault, + &vault_token_account, + &staker_token_account, + actual_withdraw_amount, + )?; + vault_token_account.reload()?; + staker_token_account.reload()?; + + // Decrement the amount reserved for withdraw tickets because it's been claimed now + vault_delegation_list + .vault_delegation_list_mut() + .decrement_amount_withdrawable_by_tickets(original_redemption_amount)?; + + // refresh after burn + vault + .vault_mut() + .set_tokens_deposited(vault_token_account.token_account().amount); + vault.vault_mut().set_lrt_supply(lrt_mint.mint().supply); + + vault.save()?; + vault_delegation_list.save()?; + + close_program_account( + program_id, + vault_staker_withdraw_ticket.account(), + staker.account(), + )?; + _close_token_account( + program_id, + &vault, + &staker, + &vault_staker_withdraw_ticket, + &vault_staker_withdraw_ticket_token_account, + &staker_lrt_token_account, + )?; + + Ok(()) +} + +/// transfers all remaining assets to the staker + closes the account +fn _close_token_account<'a, 'info>( + program_id: &Pubkey, + vault: &SanitizedVault<'a, 'info>, + staker: &SanitizedSignerAccount<'a, 'info>, + vault_staker_withdraw_ticket: &SanitizedVaultStakerWithdrawTicket<'a, 'info>, + vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + staker_lrt_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, +) -> ProgramResult { + // TODO: combine with burn lrt method + let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + program_id, + &vault.account().key, + &staker.account().key, + &vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .base(), + ); + seeds.push(vec![bump]); + let seed_slices: Vec<&[u8]> = seeds.iter().map(|seed| seed.as_slice()).collect(); + + invoke_signed( + &transfer( + &spl_token::id(), + &vault_staker_withdraw_ticket_token_account.account().key, + &staker_lrt_token_account.account().key, + &vault.account().key, + &[], + vault_staker_withdraw_ticket_token_account + .token_account() + .amount, + )?, + &[ + vault_staker_withdraw_ticket_token_account.account().clone(), + staker_lrt_token_account.account().clone(), + vault_staker_withdraw_ticket.account().clone(), + ], + &[&seed_slices], + )?; + + invoke_signed( + &close_account( + &spl_token::id(), + vault_staker_withdraw_ticket_token_account.account().key, + staker.account().key, + staker.account().key, + &[], + )?, + &[ + vault_staker_withdraw_ticket_token_account.account().clone(), + staker.account().clone(), + staker.account().clone(), + ], + &[&seed_slices], + )?; + Ok(()) +} + +fn _transfer_vault_tokens_to_staker<'a, 'info>( + program_id: &Pubkey, + vault: &SanitizedVault<'a, 'info>, + vault_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + staker_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + amount: u64, +) -> ProgramResult { + let (_, bump, mut seeds) = Vault::find_program_address(program_id, &vault.vault().base()); + seeds.push(vec![bump]); + let seed_slices: Vec<&[u8]> = seeds.iter().map(|seed| seed.as_slice()).collect(); + + invoke_signed( + &transfer( + &spl_token::id(), + vault_token_account.account().key, + staker_token_account.account().key, + vault.account().key, + &[], + amount, + )?, + &[ + vault_token_account.account().clone(), + staker_token_account.account().clone(), + vault.account().clone(), + ], + &[&seed_slices], + )?; + Ok(()) +} + +fn _burn_lrt<'a, 'info>( + program_id: &Pubkey, + vault: &SanitizedVault<'a, 'info>, + staker: &SanitizedSignerAccount<'a, 'info>, + vault_staker_withdraw_ticket: &SanitizedVaultStakerWithdrawTicket<'a, 'info>, + vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + token_mint: &SanitizedTokenMint<'a, 'info>, + burn_amount: u64, +) -> ProgramResult { + let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + program_id, + &vault.account().key, + &staker.account().key, + &vault_staker_withdraw_ticket + .vault_staker_withdraw_ticket() + .base(), + ); + seeds.push(vec![bump]); + let seed_slices: Vec<&[u8]> = seeds.iter().map(|seed| seed.as_slice()).collect(); + + invoke_signed( + &burn( + &spl_token::id(), + vault_staker_withdraw_ticket_token_account.account().key, + &token_mint.account().key, + vault.account().key, + &[], + burn_amount, + )?, + &[ + vault_staker_withdraw_ticket_token_account.account().clone(), + token_mint.account().clone(), + vault_staker_withdraw_ticket.account().clone(), + ], + &[&seed_slices], + ) +} + +pub struct SanitizedAccounts<'a, 'info> { + config: SanitizedConfig<'a, 'info>, + vault: SanitizedVault<'a, 'info>, + vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, + vault_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + lrt_mint: SanitizedTokenMint<'a, 'info>, + staker: SanitizedSignerAccount<'a, 'info>, + staker_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + staker_lrt_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + vault_staker_withdraw_ticket: SanitizedVaultStakerWithdrawTicket<'a, 'info>, + vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + token_program: SanitizedTokenProgram<'a, 'info>, + system_program: SanitizedSystemProgram<'a, 'info>, +} + +impl<'a, 'info> SanitizedAccounts<'a, 'info> { + fn sanitize( + program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + ) -> Result, ProgramError> { + let accounts_iter = &mut accounts.iter(); + + let config = + SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; + let vault_delegation_list = SanitizedVaultDelegationList::sanitize( + program_id, + next_account_info(accounts_iter)?, + true, + vault.account().key, + )?; + let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().supported_mint(), + vault.account().key, + )?; + let lrt_mint = SanitizedTokenMint::sanitize(next_account_info(accounts_iter)?, true)?; + let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; + let staker_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().supported_mint(), + staker.account().key, + )?; + let staker_lrt_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().lrt_mint(), + staker.account().key, + )?; + let vault_staker_withdraw_ticket = SanitizedVaultStakerWithdrawTicket::sanitize( + program_id, + next_account_info(accounts_iter)?, + vault.account().key, + staker.account().key, + true, + )?; + let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().supported_mint(), + vault_staker_withdraw_ticket.account().key, + )?; + let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; + + Ok(SanitizedAccounts { + config, + vault, + vault_delegation_list, + vault_token_account, + lrt_mint, + staker, + staker_token_account, + staker_lrt_token_account, + vault_staker_withdraw_ticket, + vault_staker_withdraw_ticket_token_account, + token_program, + system_program, + }) + } +} diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdrawal.rs index 91d597f9..8bfea669 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -1,9 +1,249 @@ -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use borsh::BorshSerialize; +use jito_restaking_sanitization::{ + assert_with_msg, associated_token_account::SanitizedAssociatedTokenAccount, create_account, + empty_account::EmptyAccount, signer::SanitizedSignerAccount, + system_program::SanitizedSystemProgram, token_account::SanitizedTokenAccount, + token_program::SanitizedTokenProgram, +}; +use jito_vault_core::{ + config::SanitizedConfig, + vault::SanitizedVault, + vault_delegation_list::{SanitizedVaultDelegationList, UndelegateForWithdrawMethod}, + vault_staker_withdraw_ticket::VaultStakerWithdrawTicket, +}; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + clock::{Clock, Slot}, + entrypoint::ProgramResult, + msg, + program::invoke, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + sysvar::Sysvar, +}; +use spl_token::instruction::transfer; -pub const fn process_enqueue_withdrawal( - _program_id: &Pubkey, - _accounts: &[AccountInfo], - _amount: u64, +/// Enqueues a withdraw into the VaultStakerWithdrawTicket account, transferring the amount from the +/// staker's LRT token account to the VaultStakerWithdrawTicket LRT token account. It also queues +/// the withdrawal in the vault's delegation list. +/// +/// The most obvious options for withdrawing are calculating the redemption ratio and withdrawing +/// the exact amount of collateral from operators. This may not be ideal in the case where the LRT:token +/// ratio increases due to rewards. However, if the vault has excess collateral that isn't staked, the vault +/// can withdraw that excess and return it to the staker. If there's no excess, they can withdraw the +/// amount that was set aside for withdraw. +pub fn process_enqueue_withdrawal( + program_id: &Pubkey, + accounts: &[AccountInfo], + amount: u64, ) -> ProgramResult { + let SanitizedAccounts { + config, + vault, + mut vault_delegation_list, + vault_staker_withdraw_ticket, + vault_staker_withdraw_ticket_token_account, + staker, + staker_lrt_token_account, + base, + token_program, + system_program, + } = SanitizedAccounts::sanitize(program_id, accounts)?; + + let slot = Clock::get()?.slot; + let epoch_length = config.config().epoch_length(); + let rent = Rent::get()?; + + vault_delegation_list + .vault_delegation_list_mut() + .check_update_needed(slot, epoch_length)?; + + let fee_amount = vault.vault().calculate_withdraw_fee(amount)?; + let amount_to_vault_staker_withdraw_ticket = amount + .checked_sub(fee_amount) + .ok_or(ProgramError::ArithmeticOverflow)?; + + // Find the redemption ratio at this point in time. + // It may change in between this point in time and when the withdraw ticket is processed. + // Stakers may get back less than redemption if there were accrued rewards accrued in between + // this point and the redemption. + let amount_to_withdraw = vault + .vault() + .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; + + vault_delegation_list + .vault_delegation_list_mut() + .undelegate_for_withdraw(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; + + _create_vault_staker_withdraw_ticket( + program_id, + &vault, + &staker, + &base, + &vault_staker_withdraw_ticket, + &system_program, + &rent, + slot, + amount_to_withdraw, + amount_to_vault_staker_withdraw_ticket, + )?; + + // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account + _transfer_to_vault_staker_withdraw_ticket( + &token_program, + &staker_lrt_token_account, + &vault_staker_withdraw_ticket_token_account, + &staker, + amount_to_vault_staker_withdraw_ticket, + )?; + // TODO (LB): transfer fee_amount of the LRT from the staker to the fee account + + vault_delegation_list.save()?; + Ok(()) } + +fn _transfer_to_vault_staker_withdraw_ticket<'a, 'info>( + token_program: &SanitizedTokenProgram, + staker_lrt_token_account: &SanitizedTokenAccount<'a, 'info>, + vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + staker: &SanitizedSignerAccount<'a, 'info>, + amount: u64, +) -> ProgramResult { + invoke( + &transfer( + token_program.account().key, + staker_lrt_token_account.account().key, + vault_staker_withdraw_ticket_token_account.account().key, + staker.account().key, + &[], + amount, + )?, + &[ + staker_lrt_token_account.account().clone(), + vault_staker_withdraw_ticket_token_account.account().clone(), + staker.account().clone(), + ], + ) +} + +fn _create_vault_staker_withdraw_ticket<'a, 'info>( + program_id: &Pubkey, + vault: &SanitizedVault<'a, 'info>, + staker: &SanitizedSignerAccount<'a, 'info>, + base: &SanitizedSignerAccount<'a, 'info>, + vault_staker_withdraw_ticket_account: &EmptyAccount<'a, 'info>, + system_program: &SanitizedSystemProgram<'a, 'info>, + rent: &Rent, + slot: Slot, + amount_to_withdraw: u64, + amount_to_vault_staker_withdraw_ticket: u64, +) -> ProgramResult { + let (address, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + program_id, + vault.account().key, + staker.account().key, + base.account().key, + ); + seeds.push(vec![bump]); + + assert_with_msg( + address == *vault_staker_withdraw_ticket_account.account().key, + ProgramError::InvalidAccountData, + "Vault staker withdraw ticket is not at the correct PDA", + )?; + + let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::new( + *vault.account().key, + *staker.account().key, + *base.account().key, + amount_to_withdraw, + amount_to_vault_staker_withdraw_ticket, + slot, + bump, + ); + + msg!( + "Creating vault staker withdraw ticket: {:?}", + vault_staker_withdraw_ticket_account.account().key + ); + let serialized = vault_staker_withdraw_ticket.try_to_vec()?; + create_account( + staker.account(), + vault_staker_withdraw_ticket_account.account(), + system_program.account(), + program_id, + rent, + serialized.len() as u64, + &seeds, + )?; + vault_staker_withdraw_ticket_account + .account() + .data + .borrow_mut()[..serialized.len()] + .copy_from_slice(&serialized); + Ok(()) +} + +struct SanitizedAccounts<'a, 'info> { + config: SanitizedConfig<'a, 'info>, + vault: SanitizedVault<'a, 'info>, + vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, + vault_staker_withdraw_ticket: EmptyAccount<'a, 'info>, + vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + staker: SanitizedSignerAccount<'a, 'info>, + staker_lrt_token_account: SanitizedTokenAccount<'a, 'info>, + base: SanitizedSignerAccount<'a, 'info>, + token_program: SanitizedTokenProgram<'a, 'info>, + system_program: SanitizedSystemProgram<'a, 'info>, +} + +impl<'a, 'info> SanitizedAccounts<'a, 'info> { + /// Loads accounts for [`crate::VaultInstruction::EnqueueWithdraw`] + fn sanitize( + program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + ) -> Result, ProgramError> { + let accounts_iter = &mut accounts.iter(); + + let config = + SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; + let vault_delegation_list = SanitizedVaultDelegationList::sanitize( + program_id, + next_account_info(accounts_iter)?, + true, + vault.account().key, + )?; + let vault_staker_withdraw_ticket = + EmptyAccount::sanitize(next_account_info(accounts_iter)?, true)?; + let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().lrt_mint(), + vault_staker_withdraw_ticket.account().key, + )?; + let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; + let staker_lrt_token_account = SanitizedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().lrt_mint(), + staker.account().key, + )?; + let base = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, false)?; + let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; + + Ok(SanitizedAccounts { + config, + vault, + vault_delegation_list, + vault_staker_withdraw_ticket, + vault_staker_withdraw_ticket_token_account, + staker, + staker_lrt_token_account, + base, + token_program, + system_program, + }) + } +} diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index b2333a5e..10830486 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -3,6 +3,7 @@ mod add_delegation; mod add_operator; mod add_slasher; mod burn; +mod burn_withdraw_ticket; mod create_token_metadata; mod enqueue_withdrawal; mod initialize_config; @@ -17,8 +18,8 @@ mod set_admin; mod set_capacity; mod set_secondary_admin; mod slash; -mod update_delegations; mod update_token_metadata; +mod update_vault; mod withdrawal_asset; use borsh::BorshDeserialize; @@ -33,6 +34,7 @@ use solana_security_txt::security_txt; use crate::{ add_avs::process_vault_add_avs, add_delegation::process_add_delegation, add_operator::process_vault_add_operator, add_slasher::process_add_slasher, burn::process_burn, + burn_withdraw_ticket::process_burn_withdraw_ticket, create_token_metadata::process_create_token_metadata, enqueue_withdrawal::process_enqueue_withdrawal, initialize_config::process_initialize_config, initialize_vault::process_initialize_vault, @@ -41,9 +43,8 @@ use crate::{ remove_avs::process_vault_remove_avs, remove_delegation::process_remove_delegation, remove_operator::process_vault_remove_operator, set_admin::process_set_admin, set_capacity::process_set_capacity, set_secondary_admin::process_set_secondary_admin, - slash::process_slash, update_delegations::process_update_delegations, - update_token_metadata::process_update_token_metadata, - withdrawal_asset::process_withdrawal_asset, + slash::process_slash, update_token_metadata::process_update_token_metadata, + update_vault::process_update_vault, withdrawal_asset::process_withdrawal_asset, }; declare_id!("DVoKuzt4i8EAakix852XwSAYmXnECdhegB6EDtabp4dg"); @@ -108,7 +109,7 @@ pub fn process_instruction( msg!("Instruction: SetCapacity"); process_set_capacity(program_id, accounts, amount) } - VaultInstruction::WithdrawalAsset { amount } => { + VaultInstruction::AdminWithdraw { amount } => { msg!("Instruction: WithdrawalAsset"); process_withdrawal_asset(program_id, accounts, amount) } @@ -123,10 +124,14 @@ pub fn process_instruction( msg!("Instruction: Burn"); process_burn(program_id, accounts, amount) } - VaultInstruction::EnqueueWithdrawal { amount } => { + VaultInstruction::EnqueueWithdraw { amount } => { msg!("Instruction: EnqueueWithdrawal"); process_enqueue_withdrawal(program_id, accounts, amount) } + VaultInstruction::BurnWithdrawTicket => { + msg!("Instruction: BurnWithdrawTicket"); + process_burn_withdraw_ticket(program_id, accounts) + } // ------------------------------------------ // Vault-AVS operations // ------------------------------------------ @@ -160,9 +165,9 @@ pub fn process_instruction( msg!("Instruction: RemoveDelegation"); process_remove_delegation(program_id, accounts, amount) } - VaultInstruction::UpdateDelegations => { + VaultInstruction::UpdateVault => { msg!("Instruction: UpdateDelegations"); - process_update_delegations(program_id, accounts) + process_update_vault(program_id, accounts) } // ------------------------------------------ // Vault slashing diff --git a/vault_program/src/remove_delegation.rs b/vault_program/src/remove_delegation.rs index d575cc5c..f0300703 100644 --- a/vault_program/src/remove_delegation.rs +++ b/vault_program/src/remove_delegation.rs @@ -28,9 +28,12 @@ pub fn process_remove_delegation( vault .vault() .check_delegation_admin(delegation_admin.account().key)?; + + let slot = Clock::get()?.slot; + let epoch_length = config.config().epoch_length(); vault_delegation_list .vault_delegation_list_mut() - .update_delegations(Clock::get()?.slot, config.config().epoch_length()); + .check_update_needed(slot, epoch_length)?; vault_delegation_list .vault_delegation_list_mut() .undelegate(*operator.key, amount)?; diff --git a/vault_program/src/slash.rs b/vault_program/src/slash.rs index 3bb34385..0cb22fa5 100644 --- a/vault_program/src/slash.rs +++ b/vault_program/src/slash.rs @@ -37,6 +37,7 @@ pub fn process_slash( ) -> ProgramResult { let slot = Clock::get()?.slot; let SanitizedAccounts { + config, mut vault, operator, avs_operator_ticket, @@ -89,12 +90,14 @@ pub fn process_slash( vault_delegation_list .vault_delegation_list_mut() - .slash(operator.account().key, slash_amount)?; + .check_update_needed(slot, config.config().epoch_length())?; + vault_delegation_list + .vault_delegation_list_mut() + .slash(operator.account().key, slash_amount)?; vault_avs_slasher_operator_ticket .vault_avs_slasher_operator_ticket_mut() .increment_slashed_amount(slash_amount)?; - _transfer_slashed_funds( &vault, &vault_token_account, @@ -149,6 +152,7 @@ fn _transfer_slashed_funds<'a, 'info>( } struct SanitizedAccounts<'a, 'info> { + config: SanitizedConfig<'a, 'info>, vault: SanitizedVault<'a, 'info>, operator: SanitizedOperator<'a, 'info>, avs_operator_ticket: SanitizedAvsOperatorTicket<'a, 'info>, @@ -280,6 +284,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let _token_program = SanitizedTokenProgram::sanitize(next_account_info(&mut accounts_iter)?)?; Ok(Self { + config, vault, operator, avs_operator_ticket, diff --git a/vault_program/src/update_delegations.rs b/vault_program/src/update_delegations.rs deleted file mode 100644 index 80e30975..00000000 --- a/vault_program/src/update_delegations.rs +++ /dev/null @@ -1,52 +0,0 @@ -use jito_vault_core::{ - config::SanitizedConfig, vault::SanitizedVault, - vault_delegation_list::SanitizedVaultDelegationList, -}; -use solana_program::{ - account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, -}; - -pub fn process_update_delegations(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - let SanitizedAccounts { - config, - mut vault_delegation_list, - } = SanitizedAccounts::sanitize(program_id, accounts)?; - - let slot = Clock::get()?.slot; - vault_delegation_list - .vault_delegation_list_mut() - .update_delegations(slot, config.config().epoch_length()); - - vault_delegation_list.save()?; - - Ok(()) -} - -struct SanitizedAccounts<'a, 'info> { - config: SanitizedConfig<'a, 'info>, - vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, -} - -impl<'a, 'info> SanitizedAccounts<'a, 'info> { - fn sanitize( - program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - ) -> Result, ProgramError> { - let accounts_iter = &mut accounts.iter(); - - let config = SanitizedConfig::sanitize(program_id, accounts_iter.next().unwrap(), false)?; - let vault = SanitizedVault::sanitize(program_id, accounts_iter.next().unwrap(), false)?; - - let vault_delegation_list = SanitizedVaultDelegationList::sanitize( - program_id, - accounts_iter.next().unwrap(), - true, - vault.account().key, - )?; - Ok(SanitizedAccounts { - config, - vault_delegation_list, - }) - } -} diff --git a/vault_program/src/update_vault.rs b/vault_program/src/update_vault.rs new file mode 100644 index 00000000..8f4635e6 --- /dev/null +++ b/vault_program/src/update_vault.rs @@ -0,0 +1,75 @@ +use jito_restaking_sanitization::associated_token_account::SanitizedAssociatedTokenAccount; +use jito_vault_core::{ + config::SanitizedConfig, vault::SanitizedVault, + vault_delegation_list::SanitizedVaultDelegationList, +}; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + clock::Clock, + entrypoint::ProgramResult, + program_error::ProgramError, + pubkey::Pubkey, + sysvar::Sysvar, +}; + +pub fn process_update_vault(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + let SanitizedAccounts { + config, + mut vault, + mut vault_delegation_list, + vault_token_account, + } = SanitizedAccounts::sanitize(program_id, accounts)?; + + let slot = Clock::get()?.slot; + vault_delegation_list + .vault_delegation_list_mut() + .update(slot, config.config().epoch_length())?; + + vault + .vault_mut() + .set_tokens_deposited(vault_token_account.token_account().amount); + + vault.save()?; + vault_delegation_list.save()?; + + Ok(()) +} + +struct SanitizedAccounts<'a, 'info> { + config: SanitizedConfig<'a, 'info>, + vault: SanitizedVault<'a, 'info>, + vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, + vault_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, +} + +impl<'a, 'info> SanitizedAccounts<'a, 'info> { + fn sanitize( + program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + ) -> Result, ProgramError> { + let accounts_iter = &mut accounts.iter(); + + let config = + SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; + + let vault_delegation_list = SanitizedVaultDelegationList::sanitize( + program_id, + next_account_info(accounts_iter)?, + true, + vault.account().key, + )?; + let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().supported_mint(), + vault.account().key, + )?; + + Ok(SanitizedAccounts { + config, + vault, + vault_delegation_list, + vault_token_account, + }) + } +} diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index 82931aa8..f04e4583 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -93,10 +93,35 @@ pub enum VaultInstruction { /// Enqueues a withdrawal of LRT tokens /// Used when there aren't enough idle assets in the vault to cover a withdrawal - EnqueueWithdrawal { + #[account(0, name = "config")] + #[account(1, writable, name = "vault")] + #[account(2, writable, name = "vault_delegation_list")] + #[account(3, writable, name = "vault_staker_withdraw_ticket")] + #[account(4, writable, name = "vault_staker_withdraw_ticket_token_account")] + #[account(5, writable, signer, name = "staker")] + #[account(6, writable, name = "staker_lrt_token_account")] + #[account(7, signer, name = "base")] + #[account(8, name = "token_program")] + #[account(9, name = "system_program")] + EnqueueWithdraw { amount: u64 }, + /// Burns the withdraw ticket, returning funds to the staker. Withdraw tickets can be burned + /// after one full epoch of being enqueued. + #[account(0, name = "config")] + #[account(1, writable, name = "vault")] + #[account(2, writable, name = "vault_delegation_list")] + #[account(3, writable, name = "vault_token_account")] + #[account(4, writable, name = "lrt_mint")] + #[account(5, writable, signer, name = "staker")] + #[account(6, writable, name = "staker_token_account")] + #[account(7, writable, name = "vault_staker_withdraw_ticket")] + #[account(8, writable, name = "vault_staker_withdraw_ticket_token_account")] + #[account(9, name = "token_program")] + #[account(10, name = "system_program")] + BurnWithdrawTicket, + /// Sets the max tokens that can be deposited into the LRT #[account(0, writable, name = "vault")] #[account(1, signer, name = "admin")] @@ -105,7 +130,7 @@ pub enum VaultInstruction { }, /// Withdraws any non-backing tokens from the vault - WithdrawalAsset { + AdminWithdraw { amount: u64 }, @@ -143,12 +168,12 @@ pub enum VaultInstruction { amount: u64, }, - /// Updates delegations at epoch boundaries + /// Updates the vault #[account(0, name = "config")] - #[account(1, name = "vault")] + #[account(1, writable, name = "vault")] #[account(2, writable, name = "vault_delegation_list")] - #[account(3, writable, signer, name = "payer")] - UpdateDelegations, + #[account(3, writable, name = "vault_token_account")] + UpdateVault, /// Registers a slasher with the vault #[account(0, name = "config")] @@ -420,7 +445,7 @@ pub fn enqueue_withdrawal(program_id: &Pubkey, amount: u64) -> Instruction { Instruction { program_id: *program_id, accounts: vec![], - data: VaultInstruction::EnqueueWithdrawal { amount } + data: VaultInstruction::EnqueueWithdraw { amount } .try_to_vec() .unwrap(), } @@ -449,7 +474,7 @@ pub fn withdrawal_asset(program_id: &Pubkey, amount: u64) -> Instruction { Instruction { program_id: *program_id, accounts: vec![], - data: VaultInstruction::WithdrawalAsset { amount } + data: VaultInstruction::AdminWithdraw { amount } .try_to_vec() .unwrap(), } @@ -567,7 +592,7 @@ pub fn update_delegations( Instruction { program_id: *program_id, accounts, - data: VaultInstruction::UpdateDelegations.try_to_vec().unwrap(), + data: VaultInstruction::UpdateVault.try_to_vec().unwrap(), } } From f6a19e8704236c408271588a57f40ba242485cee Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 16:24:15 -0500 Subject: [PATCH 02/22] lint --- vault_core/src/operator_delegation.rs | 4 ++- vault_program/src/burn_withdraw_ticket.rs | 31 +++++++++-------------- vault_program/src/enqueue_withdrawal.rs | 1 + 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 96ec234e..7fbe97e5 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -115,7 +115,9 @@ impl OperatorDelegation { if *amount == 0 || remaining_slash == 0 { return Ok(()); } - let pro_rata_slash = ((*amount as u128) * (slash_amount as u128)) + let pro_rata_slash = (*amount as u128) + .checked_mul(slash_amount as u128) + .ok_or(VaultCoreError::VaultSlashingOverflow)? .checked_div(total_security_amount as u128) .ok_or(VaultCoreError::VaultSlashingDivisionByZero)?; let actual_slash = std::cmp::min( diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs index fafb779a..e2cfd717 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -35,8 +35,6 @@ pub fn process_burn_withdraw_ticket( staker_lrt_token_account, vault_staker_withdraw_ticket, mut vault_staker_withdraw_ticket_token_account, - token_program: _, - system_program: _, } = SanitizedAccounts::sanitize(program_id, accounts)?; let slot = Clock::get()?.slot; @@ -92,10 +90,9 @@ pub fn process_burn_withdraw_ticket( .ok_or(ProgramError::ArithmeticOverflow)?; // Determine the actual amount to withdraw - let actual_withdrawal_amount = original_redemption_amount + original_redemption_amount .checked_add(extra_amount.min(available_unstaked_assets)) - .ok_or(ProgramError::ArithmeticOverflow)?; - actual_withdrawal_amount + .ok_or(ProgramError::ArithmeticOverflow)? } else { redemption_amount }; @@ -175,8 +172,8 @@ fn _close_token_account<'a, 'info>( // TODO: combine with burn lrt method let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( program_id, - &vault.account().key, - &staker.account().key, + vault.account().key, + staker.account().key, &vault_staker_withdraw_ticket .vault_staker_withdraw_ticket() .base(), @@ -187,9 +184,9 @@ fn _close_token_account<'a, 'info>( invoke_signed( &transfer( &spl_token::id(), - &vault_staker_withdraw_ticket_token_account.account().key, - &staker_lrt_token_account.account().key, - &vault.account().key, + vault_staker_withdraw_ticket_token_account.account().key, + staker_lrt_token_account.account().key, + vault.account().key, &[], vault_staker_withdraw_ticket_token_account .token_account() @@ -262,8 +259,8 @@ fn _burn_lrt<'a, 'info>( ) -> ProgramResult { let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( program_id, - &vault.account().key, - &staker.account().key, + vault.account().key, + staker.account().key, &vault_staker_withdraw_ticket .vault_staker_withdraw_ticket() .base(), @@ -275,7 +272,7 @@ fn _burn_lrt<'a, 'info>( &burn( &spl_token::id(), vault_staker_withdraw_ticket_token_account.account().key, - &token_mint.account().key, + token_mint.account().key, vault.account().key, &[], burn_amount, @@ -300,8 +297,6 @@ pub struct SanitizedAccounts<'a, 'info> { staker_lrt_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, vault_staker_withdraw_ticket: SanitizedVaultStakerWithdrawTicket<'a, 'info>, vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, - token_program: SanitizedTokenProgram<'a, 'info>, - system_program: SanitizedSystemProgram<'a, 'info>, } impl<'a, 'info> SanitizedAccounts<'a, 'info> { @@ -349,8 +344,8 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { &vault.vault().supported_mint(), vault_staker_withdraw_ticket.account().key, )?; - let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; - let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; + let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + let _system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; Ok(SanitizedAccounts { config, @@ -363,8 +358,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { staker_lrt_token_account, vault_staker_withdraw_ticket, vault_staker_withdraw_ticket_token_account, - token_program, - system_program, }) } } diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdrawal.rs index 8bfea669..85e03c18 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -128,6 +128,7 @@ fn _transfer_to_vault_staker_withdraw_ticket<'a, 'info>( ) } +#[allow(clippy::too_many_arguments)] fn _create_vault_staker_withdraw_ticket<'a, 'info>( program_id: &Pubkey, vault: &SanitizedVault<'a, 'info>, From 480bf2b81a801cc8341adfed7c37e3033d6a3ad3 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 22:03:23 -0500 Subject: [PATCH 03/22] wip wtf --- Cargo.lock | 446 ++++++++++----- Cargo.toml | 6 +- integration_tests/Cargo.toml | 2 + integration_tests/tests/fixtures/fixture.rs | 108 ++-- .../tests/fixtures/restaking_client.rs | 93 +++- .../tests/fixtures/vault_client.rs | 506 ++++++++++++++++-- integration_tests/tests/vault/add_avs.rs | 145 ++--- .../tests/vault/enqueue_withdrawal.rs | 30 ++ .../tests/vault/initialize_config.rs | 14 +- .../tests/vault/initialize_vault.rs | 58 +- integration_tests/tests/vault/mod.rs | 1 + vault_core/src/vault.rs | 4 +- vault_program/src/add_avs.rs | 8 + ...ueue_withdrawal.rs => enqueue_withdraw.rs} | 12 +- vault_program/src/lib.rs | 8 +- vault_sdk/src/lib.rs | 44 +- 16 files changed, 1094 insertions(+), 391 deletions(-) create mode 100644 integration_tests/tests/vault/enqueue_withdrawal.rs rename vault_program/src/{enqueue_withdrawal.rs => enqueue_withdraw.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index 2e8eb74d..c6bcdc41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d5a2cecb58716e47d67d5703a249964b14c7be1ec3cad3affc295b2d1c35d" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom 0.2.12", @@ -147,6 +147,20 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-bn254" version = "0.4.0" @@ -520,6 +534,16 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive 1.5.1", + "cfg_aliases", +] + [[package]] name = "borsh-derive" version = "0.9.3" @@ -546,6 +570,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.55", + "syn_derive", +] + [[package]] name = "borsh-derive-internal" version = "0.9.3" @@ -712,6 +750,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.35" @@ -1006,12 +1050,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "4.0.2" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "num_cpus", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", "rayon", ] @@ -1082,6 +1129,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -1145,6 +1198,12 @@ dependencies = [ "syn 2.0.55", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "eager" version = "0.1.0" @@ -1321,6 +1380,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1337,13 +1405,10 @@ dependencies = [ ] [[package]] -name = "fs-err" -version = "2.11.0" +name = "fragile" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures" @@ -1550,7 +1615,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.5", + "ahash 0.8.11", ] [[package]] @@ -1749,6 +1814,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "index_list" version = "0.2.11" @@ -1860,6 +1944,7 @@ dependencies = [ "jito-vault-core", "jito-vault-program", "jito-vault-sdk", + "log", "shank", "solana-program", "solana-program-test", @@ -2218,6 +2303,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "modular-bitfield" version = "0.11.2" @@ -2262,6 +2374,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num" version = "0.2.1" @@ -2675,6 +2793,36 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3238,6 +3386,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +[[package]] +name = "seqlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] + [[package]] name = "serde" version = "1.0.197" @@ -3488,9 +3645,9 @@ dependencies = [ [[package]] name = "solana-account-decoder" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9721ff8b83683cb7dd856079cde8a8010bfd282d482699a867ad50ea9e727e" +checksum = "5e621af8405ad85f2c3822bb1794f57389b7bc2e8c8c49e0d5a38b85b8955565" dependencies = [ "Inflector", "base64 0.21.7", @@ -3513,9 +3670,9 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cdae5f9d2562360bd07fde58976f6ff8f7d90c8311bc5a4959edd7ba61ccec4" +checksum = "b420b71476b1c5acd3552ecd5c6e360f46a262c972d02dac1a7d5eacb2d5adf0" dependencies = [ "arrayref", "bincode", @@ -3528,7 +3685,6 @@ dependencies = [ "dashmap", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -3537,10 +3693,10 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -3548,14 +3704,17 @@ dependencies = [ "rayon", "regex", "rustc_version", + "seqlock", "serde", "serde_derive", + "smallvec", "solana-bucket-map", "solana-config-program", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-measure", "solana-metrics", + "solana-nohash-hasher", "solana-program-runtime", "solana-rayon-threadlimit", "solana-sdk", @@ -3572,14 +3731,14 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c471a3f150762652b5d77f83432b8574738e621fae4885c47132cfe33676d27c" +checksum = "76ed10ab9edfb4bf390cf15c3f5fb075a78b202e4041af3db4d3fd899c4adfc5" dependencies = [ "bincode", "bytemuck", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -3593,11 +3752,11 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4397a1bcefabb65c426c5baea8c5df1da2dff734ea9328789253e30e59bab25" +checksum = "704018ba89029425150d17ae69d668e3ecb076151d3446cf99862f7ca491f8b0" dependencies = [ - "borsh 0.10.3", + "borsh 1.5.1", "futures", "solana-banks-interface", "solana-program", @@ -3610,9 +3769,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61b939343909a74cc826326dfdcad16f6015301e3d2134bc29fee59c27171a9" +checksum = "100f1e18910f6c666d8860a675c2cc6a3e4f92c1b99b6948192da8fe1b6156f6" dependencies = [ "serde", "solana-sdk", @@ -3621,9 +3780,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b803ea31aa9b17a679454c921af320f9efb026355cb067dfbad57261ab771593" +checksum = "ac37078f40d968d3e47156ec5f65b392dba76618777c6f7fa915ced84a8f3f99" dependencies = [ "bincode", "crossbeam-channel", @@ -3641,9 +3800,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a99a75cdd7d4e40b51d8de8125f3d44db3f7735d96f4e3f31ee79d3110a8f4d" +checksum = "38d5e8dc69cfae82adf204822398688bc89ad1849964231c28037d7e5bede949" dependencies = [ "bincode", "byteorder", @@ -3660,16 +3819,16 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9830a7dbea75c0cbd62a4ca31bcd9b8832fbdb915c949740a44a496630fb5eb6" +checksum = "af5cd35ff8bc73727f1eab457e0a3e97d683fcfbef2a324981a3b62d1fe17c15" dependencies = [ "bv", "bytemuck", "log", "memmap2", "modular-bitfield", - "num_enum 0.6.1", + "num_enum 0.7.2", "rand 0.8.5", "solana-measure", "solana-sdk", @@ -3678,9 +3837,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af0fe4584d7692f24e573b17a5245cfc0d99a4e351b0185dfe02baf52f99864" +checksum = "cfefc0f73fab9535c91c5bc4077ca21ce0b966df1fece0c39e5ff3f5617a9f78" dependencies = [ "chrono", "clap 2.34.0", @@ -3695,9 +3854,9 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250ffcda91bc30c2adcc290c9b7c796f59884f1043ae3bcf3245f05158c7986c" +checksum = "398c11649c8a1918e84935ca4fbcfe1bcb55ba1aa83d3051994595ba6833d8c6" dependencies = [ "async-trait", "bincode", @@ -3728,9 +3887,9 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8703a92306dfdc4760e18db14b2f8b36c9769e5c47f0656bc77355875674e079" +checksum = "36964b228f72bac36a820acde67cd5c80d07af26d30fdf6fe83586a139c4f53c" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -3738,9 +3897,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16fd2daba039bad58277ec88143b1fe624f2352d8ccb2a80bba3693de61df440" +checksum = "5544b247de63cb2a90f8d8dbbe61468405ed9895d245c1e28f7806c918fe0fb4" dependencies = [ "bincode", "chrono", @@ -3752,9 +3911,9 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868e7fb550af97ad6fc283a5628f8ffb6dafd590dc30254163e7c9003f13ceb0" +checksum = "8e3f0556a82d5da2693159f150f3da5b5bdbf2fc0e8b3181b5ed9347096074e8" dependencies = [ "async-trait", "bincode", @@ -3774,9 +3933,9 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b79a349b86d02948720e4416463d50fd4f828b6a63512e47b497613df0d720" +checksum = "6bcac1b4ae9a7ed8cb8578d448d713ef4b98cfb7c8cc3ba76fac94b7631b6b9b" dependencies = [ "lazy_static", "log", @@ -3798,17 +3957,13 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c484f952f006e37a1a2598aebcc4dcfd48478c03fc2ce2d99787a5c78f248" +checksum = "0f0c52f25d28a1b5af57d5de3802e49883f68792a343101a9daf9817d8dfffff" dependencies = [ - "ahash 0.8.5", - "blake3", "block-buffer 0.10.4", "bs58", "bv", - "byteorder", - "cc", "either", "generic-array", "im", @@ -3819,7 +3974,6 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "serde_json", "sha2 0.10.8", "solana-frozen-abi-macro", "subtle", @@ -3828,9 +3982,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe4e1dc5fd61ac10c304b3eb8ddb49737b13e975281d623a6083cf5cf0a8616" +checksum = "fc7d0abca44a21c83305e8650341f67ccc919a1a9dd698ddafc86e78b74b9296" dependencies = [ "proc-macro2", "quote", @@ -3840,9 +3994,9 @@ dependencies = [ [[package]] name = "solana-loader-v4-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad035401037e6e7d1dc19c0ae4b613b36632b60443d28716355a4e0041892fa2" +checksum = "504108d5cb774abf89d0cf673ab6950bfe257aadf2fa872adb32b1473f7818f5" dependencies = [ "log", "solana-measure", @@ -3853,9 +4007,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b4f281f2263de6d9cc65680b90b6bd2c93ba7de573db83fc86e2d05ca7ea8c" +checksum = "d5bc0644a410b8bff84ad18541a176f27776062b1ad116cb315c6f5f14b94fed" dependencies = [ "env_logger", "lazy_static", @@ -3864,9 +4018,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a3ed981230b6a3a8aebf9f03424f5c1ff98be75cc7f0ecb153879e8e439aea" +checksum = "3b2a96265160981b5aab8a73c9830f2cd8ce7b557790d19c18cabb8565563f2f" dependencies = [ "log", "solana-sdk", @@ -3874,9 +4028,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb65329e6716dc0ce5b1d719d6e0052e7ff1179ab361916f256054d6b846ee1" +checksum = "d140890d4f906315533de1c9b929e1b3b8f4a0515f45a85ee90859778ea6e6ab" dependencies = [ "crossbeam-channel", "gethostname", @@ -3889,9 +4043,9 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8345ffe2bd2a25a02e5dea046bbbf58f81f6b44ee97224474eae1c0ea03b520a" +checksum = "024848a9557dc5054ed96c3532b95ddefb753d68b677eb0495f69cc456160e8a" dependencies = [ "bincode", "clap 3.2.25", @@ -3909,13 +4063,19 @@ dependencies = [ "url", ] +[[package]] +name = "solana-nohash-hasher" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + [[package]] name = "solana-perf" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0326bd89c2453bf2a408ff676cff4ed0080bff2a5c6543db5f62423bc23ad6e8" +checksum = "599733dcb745a7a346bc9ea540603078c99aa97e2da7395a269774a92231ebd4" dependencies = [ - "ahash 0.8.5", + "ahash 0.8.11", "bincode", "bv", "caps", @@ -3940,9 +4100,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93dc0f422549c23c4464eaa9383f4b09cd92b50dea750731dd3c31d3ee2d310f" +checksum = "76a99c3bc26fda40f42db98494271bda4906b0d7ab55f3ea7755fa5f110f58b5" dependencies = [ "ark-bn254", "ark-ec", @@ -3954,6 +4114,7 @@ dependencies = [ "blake3", "borsh 0.10.3", "borsh 0.9.3", + "borsh 1.5.1", "bs58", "bv", "bytemuck", @@ -3971,7 +4132,7 @@ dependencies = [ "log", "memoffset 0.9.1", "num-bigint 0.4.4", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "parking_lot", "rand 0.8.5", @@ -3994,9 +4155,9 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b407440b708ffda97e25b8cb977f72d1bb7466c28ea5f58751613df0af8d576" +checksum = "11ae79e82b97a66ff00770d9190abc7e6b610c108254cd84fcd0f100a1c4d165" dependencies = [ "base64 0.21.7", "bincode", @@ -4005,7 +4166,7 @@ dependencies = [ "itertools", "libc", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "percentage", "rand 0.8.5", @@ -4022,9 +4183,9 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a49b6bef92bce799f1d023e0083ace2a20720330bfe8e36dcf793ee4eb95ffe" +checksum = "48bad4093f3dc41967e33e644e09649fb86eabb67ed731cdb18b1303d053a48c" dependencies = [ "assert_matches", "async-trait", @@ -4052,9 +4213,9 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f82da20e0c0bb39f9123cf61a6e3c76a8e30b0f09e5215decfb7f6b54734f04" +checksum = "de7e67897cf69c207977eaf7923541d026b3de92f313df5f625461609c618325" dependencies = [ "crossbeam-channel", "futures-util", @@ -4077,9 +4238,9 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8478a6b4f2c2b8fa330d54ff22386a1792498cb6e84ce3893840337a992e47c6" +checksum = "13b3ca213d9c133d28484ac0eb1c741078d2495ec15fcc62f55357529147b528" dependencies = [ "async-mutex", "async-trait", @@ -4104,9 +4265,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df0aac27af8e94f907f6a65305e0a7909800336e7108bd3ce1dc1f3ef011a20" +checksum = "379bd42e389ae50f7b9c6e4a32f7b2c6f1eb41f064d76158b50003877d4c7844" dependencies = [ "lazy_static", "num_cpus", @@ -4114,14 +4275,14 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a97d60701d3fe0fde4871c5856da9e52ba733fbcfb030482278c3af4ffa0bf3" +checksum = "d55362fbec27c74974fe9c5a0c89d8fb082d9a5063c9ca7ce30857b61d8003ad" dependencies = [ "console", "dialoguer", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "parking_lot", "qstring", @@ -4133,9 +4294,9 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b750c49be9dd90981f3c12c8357d0b0c847bf5b8c638c3eff3c09f6ea199393" +checksum = "d4dc7051e6e999041ac99c011f253012d594325b244ab9b2fd349dee1c1f2276" dependencies = [ "async-trait", "base64 0.21.7", @@ -4159,9 +4320,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314d0f87fc7bab16b1b4674c03d4e92c7008fa181b67210780542c0e6cbfce38" +checksum = "4c7c5df5da53559461efae1ff370f6d89866246fc1dcbb3c3cb86b6d91cb565a" dependencies = [ "base64 0.21.7", "bs58", @@ -4181,9 +4342,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acf79164ae52ee1ee85cd7aeb1bb715a9f3b90678ee00501896d173dbc31731" +checksum = "7c7e57bc4da80bf225ba4835f876c07f66d1fe37b765efc8250beaf05202be46" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -4194,10 +4355,11 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3dfd09f3cd529ab56b33a1b389b449ddc31b338f062822c7cc30375a2f234b" +checksum = "70146346ffa147cc48238bac5bc75defd0016a217baf640957565c12701cccfe" dependencies = [ + "aquamarine", "arrayref", "base64 0.21.7", "bincode", @@ -4211,7 +4373,6 @@ dependencies = [ "dir-diff", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -4220,11 +4381,12 @@ dependencies = [ "lru", "lz4", "memmap2", + "mockall", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -4235,7 +4397,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "siphasher", "solana-accounts-db", "solana-address-lookup-table-program", "solana-bpf-loader-program", @@ -4271,15 +4432,15 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3892ee0e2acdfbeae7315db6c7c56356384631deb943377de5957074bd2dc4d1" +checksum = "90a8affb5b3acd43f7164368b88243bf091e46eccff9bea5b743e4691d7c2600" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", "bitflags 2.5.0", - "borsh 0.10.3", + "borsh 1.5.1", "bs58", "bytemuck", "byteorder", @@ -4296,9 +4457,9 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", - "num_enum 0.6.1", + "num_enum 0.7.2", "pbkdf2 0.11.0", "qstring", "qualifier_attr", @@ -4313,6 +4474,7 @@ dependencies = [ "serde_with", "sha2 0.10.8", "sha3 0.10.8", + "siphasher", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", @@ -4325,9 +4487,9 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8019cc997f6c07f09b23dfeb2c45530fa94df2e2fb9d654f3c772c9766a1511f" +checksum = "4c2ac8d7ef471476164edb2ed105bcf26071835a15f0e74703929d4ec913676b" dependencies = [ "bs58", "proc-macro2", @@ -4344,9 +4506,9 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4de519acd09b182fe8a56014a5cc9d9f676e5217e0e8e61c1ae2af777e7e0fa" +checksum = "2cb273c3f59950d747ac54396babcc6aca09fcb6ac40c83313ce4a65eb4ea208" dependencies = [ "crossbeam-channel", "log", @@ -4360,9 +4522,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c21a028ed8abed4a7a52b8410875d799b31b58489653e05ae885e2ba799463" +checksum = "ddeec78f830d8d91d4185d4cb92760efcc757013c57347de1dfd19cb3bfd07c1" dependencies = [ "bincode", "log", @@ -4375,9 +4537,9 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "067d167db6be6f25c21e4c615607757ae7161b20e08197ec2d1a63360e522069" +checksum = "3027d10aacba000eca43ac59acfec0069f93dbe3ea372d94eb7d25ed490b396d" dependencies = [ "async-channel", "bytes", @@ -4408,9 +4570,9 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4fa662731817f995cd114ce44d780be2814982a7674540be8d1c596a2d1ca43" +checksum = "244a998e66934e5a6283f69500da23af5c1d09c1f8493bc1b04d3b2f1553dad9" dependencies = [ "bincode", "log", @@ -4422,9 +4584,9 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0815c6d64a4bd71fd92ce33e82c24f5e8a5b105b26619521c4a8a43ab7573a9a" +checksum = "6e49f48b5734d90c9cc6022a2b3dcf31a8794d565850a25ff5b9820e16bd40e9" dependencies = [ "bincode", "log", @@ -4437,9 +4599,9 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576431a39a612dac9387fecb5e80fe58989b740337a07bca41f0d02695e415f9" +checksum = "474f9cd741f564f8a92b7a73da1c83ceb80137bad1b25f403bc1c57468c3e6a3" dependencies = [ "async-trait", "bincode", @@ -4461,9 +4623,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d8e2d97a8569afba70a105b402057b253fd9caee92c7ed2c3e1c3e0173fe91" +checksum = "65cbdbde85fff3b4c2e134f6bd70d26c0cc9cd2b63892be2afd424a21e2b316b" dependencies = [ "Inflector", "base64 0.21.7", @@ -4486,9 +4648,9 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8297b04cb335ffac5b4a2c727dd82a2f72c8a96e3d5e1c159e01866b74a17e50" +checksum = "e1263d731f2ee858b36fc6dac56be7fe7dc46fe223de6867c8a957cd587d565e" dependencies = [ "async-trait", "solana-connection-cache", @@ -4501,9 +4663,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84a9f9ff3feebfaa960ac5387b133c6ef2c266779cb4932aed6acac5b83cb7b" +checksum = "c24d542f3b834717e81ac0d44905d3efa27ddf21c265a163e8b54ea0358204f5" dependencies = [ "log", "rustc_version", @@ -4517,9 +4679,9 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a2355a2b40d7b0b3a9ec5a5bd2196494c620a287ce6e4234cc4722fcb0cbb5" +checksum = "a11718909a64a22de2745be42f4f3f195122b3da8b028af75e3139198da0a373" dependencies = [ "crossbeam-channel", "itertools", @@ -4536,13 +4698,13 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab0fe8455b2f06016cddefe7e3a7b9c0a57661d44816a4105c81f1088cfc1ac" +checksum = "8f26743a7027077e88cd4b136099c926dc04b38dc638e88223e88c5d05b3374a" dependencies = [ "bincode", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -4558,12 +4720,12 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad624adcac620e41d052b29705f30c003b2e396ebca0145c1d296a1ef13f5aa7" +checksum = "cdfb619937c8cda3c862151473cf1a8443ac5b2e75a5b8a8675fcf9b1f0aa08c" dependencies = [ "bytemuck", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "solana-program-runtime", "solana-sdk", @@ -4572,9 +4734,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.17.34" +version = "1.18.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a06e103ed5adf1664f9cca3d1c64edad8b5b603255d8a5dd21a4ac5d86b89c" +checksum = "8cf73fd7e430bf3269539c2fb1d05af0df26fc3b7bc090172ceda5cd4340657c" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -4586,7 +4748,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -4921,6 +5083,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -5027,6 +5201,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "test-case" version = "3.3.1" diff --git a/Cargo.toml b/Cargo.toml index 645e0392..dd9175d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,9 +44,9 @@ jito-vault-sdk = { path = "vault_sdk", version = "=0.0.1" } jito-vault-program = { path = "vault_program", version = "=0.0.1" } jito-restaking-program = { path = "restaking_program", version = "=0.0.1" } shank = "0.4.2" -solana-program = "~1.17" -solana-program-test = "~1.17" -solana-sdk = "~1.17" +solana-program = "~1.18" +solana-program-test = "~1.18" +solana-sdk = "~1.18" solana-security-txt = "1.1.1" spl-token = { version = "4.0.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "2.2.0", features = ["no-entrypoint"] } diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 4a512098..3ad394b2 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -27,3 +27,5 @@ spl-associated-token-account = { workspace = true } spl-token = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } +[dependencies] +log = "0.4.21" \ No newline at end of file diff --git a/integration_tests/tests/fixtures/fixture.rs b/integration_tests/tests/fixtures/fixture.rs index 0fa070b3..9ba2419f 100644 --- a/integration_tests/tests/fixtures/fixture.rs +++ b/integration_tests/tests/fixtures/fixture.rs @@ -1,14 +1,17 @@ -use std::fmt::{Debug, Formatter}; - +use borsh::BorshSerialize; use solana_program::{ + clock::Clock, native_token::sol_to_lamports, program_pack::Pack, pubkey::Pubkey, rent::Rent, system_instruction::{create_account, transfer}, }; -use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; +use solana_program_test::{ + processor, BanksClient, BanksClientError, ProgramTest, ProgramTestContext, +}; use solana_sdk::{ + account::AccountSharedData, commitment_config::CommitmentLevel, signature::{Keypair, Signer}, transaction::Transaction, @@ -20,6 +23,8 @@ use spl_token::{ instruction::initialize_mint2, state::{Account, Mint}, }; +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; use crate::fixtures::{restaking_client::RestakingProgramClient, vault_client::VaultProgramClient}; @@ -51,24 +56,56 @@ impl TestBuilder { Self { context } } - // pub async fn store_account( - // &mut self, - // pubkey: &Pubkey, - // owner: &Pubkey, - // data: &T, - // ) -> Result<(), BanksClientError> { - // let rent: Rent = self.context.banks_client.get_sysvar().await?; - // - // let serialized = data.try_to_vec().unwrap(); - // let mut data = AccountSharedData::new( - // rent.minimum_balance(serialized.len()), - // serialized.len(), - // owner, - // ); - // data.set_data_from_slice(serialized.as_slice()); - // self.context.set_account(pubkey, &data); - // Ok(()) - // } + pub async fn store_borsh_account( + &mut self, + pubkey: &Pubkey, + owner: &Pubkey, + data: &T, + ) -> Result<(), BanksClientError> { + let rent: Rent = self.context.banks_client.get_sysvar().await?; + + let serialized = data.try_to_vec().unwrap(); + let mut data = AccountSharedData::new( + rent.minimum_balance(serialized.len()), + serialized.len(), + owner, + ); + data.set_data_from_slice(serialized.as_slice()); + self.context.set_account(pubkey, &data); + Ok(()) + } + + pub async fn get_account( + &mut self, + pubkey: &Pubkey, + ) -> Result { + let account = self + .context + .banks_client + .get_account_with_commitment(*pubkey, CommitmentLevel::Processed) + .await? + .unwrap(); + Ok(account) + } + + pub async fn store_account( + &mut self, + pubkey: &Pubkey, + owner: &Pubkey, + data: &[u8], + ) -> Result<(), BanksClientError> { + let rent: Rent = self.context.banks_client.get_sysvar().await?; + + let serialized = data.to_vec(); + let mut data = AccountSharedData::new( + rent.minimum_balance(serialized.len()), + serialized.len(), + owner, + ); + data.set_data_from_slice(serialized.as_slice()); + self.context.set_account(pubkey, &data); + Ok(()) + } pub async fn transfer(&mut self, to: &Pubkey, sol: f64) -> Result<(), BanksClientError> { let blockhash = self.context.banks_client.get_latest_blockhash().await?; @@ -90,11 +127,6 @@ impl TestBuilder { .await } - // pub async fn get_mint(&mut self, mint: &Pubkey) -> Result { - // let account = self.context.banks_client.get_account(*mint).await?.unwrap(); - // Ok(Mint::unpack(&account.data).unwrap()) - // } - pub async fn get_token_account( &mut self, token_account: &Pubkey, @@ -204,19 +236,25 @@ impl TestBuilder { .await } - // pub async fn warp_to_next_slot(&mut self) -> Result<(), BanksClientError> { - // let clock: Clock = self.context.banks_client.get_sysvar().await?; - // self.context - // .warp_to_slot(clock.slot.checked_add(1).unwrap()) - // .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; - // Ok(()) - // } + pub async fn warp_to_next_slot(&mut self) -> Result<(), BanksClientError> { + let clock: Clock = self.context.banks_client.get_sysvar().await?; + self.context + .warp_to_slot(clock.slot.checked_add(1).unwrap()) + .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; + Ok(()) + } pub fn vault_program_client(&self) -> VaultProgramClient { - VaultProgramClient::new(self.context.banks_client.clone()) + VaultProgramClient::new( + self.context.banks_client.clone(), + self.context.payer.insecure_clone(), + ) } pub fn restaking_program_client(&self) -> RestakingProgramClient { - RestakingProgramClient::new(self.context.banks_client.clone()) + RestakingProgramClient::new( + self.context.banks_client.clone(), + self.context.payer.insecure_clone(), + ) } } diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs index 6f0a0362..69c9c27c 100644 --- a/integration_tests/tests/fixtures/restaking_client.rs +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -9,7 +9,7 @@ use jito_restaking_sdk::{ avs_add_operator, avs_add_vault, avs_add_vault_slasher, initialize_avs, initialize_config, initialize_operator, operator_add_avs, operator_add_vault, }; -use solana_program::pubkey::Pubkey; +use solana_program::{native_token::sol_to_lamports, pubkey::Pubkey, system_instruction::transfer}; use solana_program_test::{BanksClient, BanksClientError}; use solana_sdk::{ commitment_config::CommitmentLevel, @@ -17,17 +17,30 @@ use solana_sdk::{ transaction::Transaction, }; +pub struct AvsRoot { + pub avs_pubkey: Pubkey, + pub avs_admin: Keypair, +} + pub struct RestakingProgramClient { banks_client: BanksClient, + payer: Keypair, } impl RestakingProgramClient { - pub const fn new(banks_client: BanksClient) -> Self { - Self { banks_client } + pub const fn new(banks_client: BanksClient, payer: Keypair) -> Self { + Self { + banks_client, + payer, + } } pub async fn get_avs(&mut self, avs: &Pubkey) -> Result { - let account = self.banks_client.get_account(*avs).await?.unwrap(); + let account = self + .banks_client + .get_account_with_commitment(*avs, CommitmentLevel::Processed) + .await? + .unwrap(); Ok(Avs::deserialize(&mut account.data.as_slice())?) } @@ -117,6 +130,17 @@ impl RestakingProgramClient { )?) } + pub async fn setup_config(&mut self) -> Result { + let restaking_config_pubkey = Config::find_program_address(&jito_restaking_program::id()).0; + let restaking_config_admin = Keypair::new(); + + self._airdrop(&restaking_config_admin.pubkey(), 1.0).await?; + self.initialize_config(&restaking_config_pubkey, &restaking_config_admin) + .await?; + + Ok(restaking_config_admin) + } + pub async fn initialize_config( &mut self, config: &Pubkey, @@ -137,6 +161,52 @@ impl RestakingProgramClient { .await } + pub async fn setup_avs(&mut self) -> Result { + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + + self._airdrop(&avs_admin.pubkey(), 1.0).await?; + + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + self.initialize_avs( + &Config::find_program_address(&jito_restaking_program::id()).0, + &avs_pubkey, + &avs_admin, + &avs_base, + ) + .await + .unwrap(); + + Ok(AvsRoot { + avs_pubkey, + avs_admin, + }) + } + + pub async fn avs_vault_opt_in( + &mut self, + avs_root: &AvsRoot, + vault: &Pubkey, + ) -> Result<(), BanksClientError> { + let avs_vault_ticket = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), + &avs_root.avs_pubkey, + vault, + ) + .0; + + self.avs_add_vault( + &Config::find_program_address(&jito_restaking_program::id()).0, + &avs_root.avs_pubkey, + vault, + &avs_vault_ticket, + &avs_root.avs_admin, + &self.payer.insecure_clone(), + ) + .await + } + pub async fn initialize_avs( &mut self, config: &Pubkey, @@ -623,4 +693,19 @@ impl RestakingProgramClient { ) .await } + + pub async fn _airdrop(&mut self, to: &Pubkey, sol: f64) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[transfer(&self.payer.pubkey(), to, sol_to_lamports(sol))], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await + } } diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index d11443ee..58d1f315 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -1,25 +1,242 @@ use borsh::BorshDeserialize; +use jito_restaking_core::avs_vault_ticket::AvsVaultTicket; use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, vault_delegation_list::VaultDelegationList, vault_operator_ticket::VaultOperatorTicket, }; use jito_vault_sdk::{add_delegation, initialize_config, initialize_vault}; -use solana_program::pubkey::Pubkey; +use solana_program::{ + native_token::sol_to_lamports, + program_pack::Pack, + pubkey::Pubkey, + rent::Rent, + system_instruction::{create_account, transfer}, +}; use solana_program_test::{BanksClient, BanksClientError}; use solana_sdk::{ commitment_config::CommitmentLevel, signature::{Keypair, Signer}, transaction::Transaction, }; +use spl_associated_token_account::{ + get_associated_token_address, instruction::create_associated_token_account_idempotent, +}; +use spl_token::{ + instruction::initialize_mint2, + state::{Account, Mint}, +}; +// +// struct EnqueueWithdrawAccountData { +// config_pubkey: Pubkey, +// config: Config, +// +// vault_pubkey: Pubkey, +// vault: Vault, +// +// vault_delegation_list_pubkey: Pubkey, +// vault_delegation_list: VaultDelegationList, +// +// vault_staker_withdraw_ticket: Pubkey, +// +// vault_staker_withdraw_ticket_token_account: Pubkey, +// +// staker: Keypair, +// +// staker_lrt_token_account: Pubkey, +// +// base: Keypair, +// } + +// fn prepare_accounts( +// vault_index: u64, +// deposit_fee_bps: u16, +// withdrawal_fee_bps: u16, +// ) -> EnqueueWithdrawAccountData { +// let (config_pubkey, config_bump, _) = Config::find_program_address(&jito_vault_program::id()); +// let mut config = Config::new( +// Pubkey::new_unique(), +// jito_restaking_program::id(), +// config_bump, +// ); +// // assume the vault is created +// config.increment_vaults().unwrap(); +// +// let base = Pubkey::new_unique(); +// let (vault_pubkey, bump, _) = Vault::find_program_address(&jito_vault_program::id(), &base); +// let vault = Vault::new( +// Pubkey::new_unique(), +// Pubkey::new_unique(), +// Pubkey::new_unique(), +// vault_index, +// base, +// deposit_fee_bps, +// withdrawal_fee_bps, +// bump, +// ); +// +// let (vault_delegation_list_pubkey, vault_delegation_list_bump, _) = +// VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey); +// let vault_delegation_list = VaultDelegationList::new(vault_pubkey, vault_delegation_list_bump); +// +// let staker = Keypair::new(); +// +// let staker_lrt_token_account = +// get_associated_token_address(&staker.pubkey(), &vault.lrt_mint()); +// +// let base = Keypair::new(); +// +// let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::find_program_address( +// &jito_vault_program::id(), +// &vault_pubkey, +// &staker.pubkey(), +// &base.pubkey(), +// ) +// .0; +// let vault_staker_withdraw_ticket_token_account = +// get_associated_token_address(&vault_staker_withdraw_ticket, &vault.lrt_mint()); +// +// EnqueueWithdrawAccountData { +// config_pubkey, +// config, +// vault_pubkey, +// vault, +// vault_delegation_list_pubkey, +// vault_delegation_list, +// vault_staker_withdraw_ticket, +// vault_staker_withdraw_ticket_token_account, +// staker, +// staker_lrt_token_account, +// base, +// } +// } +// +// async fn write_enqueue_withdraw_accounts( +// accounts: &EnqueueWithdrawAccountData, +// fixture: &mut TestBuilder, +// ) { +// let EnqueueWithdrawAccountData { +// config_pubkey, +// config, +// vault_pubkey, +// vault, +// vault_delegation_list_pubkey, +// vault_delegation_list, +// vault_staker_withdraw_ticket: _, // created in the function +// vault_staker_withdraw_ticket_token_account, +// staker, +// staker_lrt_token_account, +// base: _, +// } = accounts; +// +// fixture +// .store_borsh_account(&config_pubkey, &jito_vault_program::id(), &config) +// .await +// .unwrap(); +// +// fixture +// .store_borsh_account(vault_pubkey, &jito_vault_program::id(), &vault) +// .await +// .unwrap(); +// let mut mint_buf = [0; Mint::LEN]; +// Mint { +// mint_authority: COption::Some(*vault_pubkey), +// supply: 0, +// decimals: 9, +// is_initialized: true, +// freeze_authority: COption::None, +// } +// .pack_into_slice(&mut mint_buf); +// fixture +// .store_account(&vault.lrt_mint(), &spl_token::id(), &mint_buf) +// .await +// .unwrap(); +// fixture +// .store_account(&vault.supported_mint(), &spl_token::id(), &mint_buf) +// .await +// .unwrap(); +// +// fixture +// .store_borsh_account( +// &VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0, +// &jito_vault_program::id(), +// &vault_delegation_list, +// ) +// .await +// .unwrap(); +// +// let mut token_account_buf = [0; Account::LEN]; +// // setup the LRT token account owned by the vault_staker_withdraw_ticket +// Account { +// mint: vault.lrt_mint(), +// owner: *vault_pubkey, +// amount: 0, +// delegate: COption::None, +// state: spl_token::state::AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut token_account_buf); +// fixture +// .store_account( +// vault_staker_withdraw_ticket_token_account, +// &spl_token::id(), +// &token_account_buf, +// ) +// .await +// .unwrap(); +// +// // setup the LRT token account owned by the staker +// Account { +// mint: vault.lrt_mint(), +// owner: staker.pubkey(), +// amount: 0, +// delegate: COption::None, +// state: spl_token::state::AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut token_account_buf); +// fixture +// .store_account( +// staker_lrt_token_account, +// &spl_token::id(), +// &token_account_buf, +// ) +// .await +// .unwrap(); +// } + +pub struct VaultRoot { + pub vault_pubkey: Pubkey, + pub vault_admin: Keypair, +} pub struct VaultProgramClient { banks_client: BanksClient, + payer: Keypair, } impl VaultProgramClient { - pub const fn new(banks_client: BanksClient) -> Self { - Self { banks_client } + pub const fn new(banks_client: BanksClient, payer: Keypair) -> Self { + Self { + banks_client, + payer, + } + } + + pub async fn get_account( + &mut self, + pubkey: &Pubkey, + ) -> Result { + let account = self + .banks_client + .get_account_with_commitment(*pubkey, CommitmentLevel::Processed) + .await? + .unwrap(); + Ok(account) } pub async fn get_config(&mut self, account: &Pubkey) -> Result { @@ -107,13 +324,25 @@ impl VaultProgramClient { )?) } + pub async fn setup_config(&mut self) -> Result { + let config_admin = Keypair::new(); + + self._airdrop(&config_admin.pubkey(), 1.0).await?; + + let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; + self.initialize_config(&config_pubkey, &config_admin) + .await?; + + Ok(config_admin) + } + pub async fn initialize_config( &mut self, config: &Pubkey, config_admin: &Keypair, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[initialize_config( &jito_vault_program::id(), &config, @@ -127,6 +356,80 @@ impl VaultProgramClient { .await } + pub async fn setup_vault( + &mut self, + deposit_fee_bps: u16, + withdraw_fee_bps: u16, + ) -> Result<(Keypair, VaultRoot), BanksClientError> { + let config_admin = self.setup_config().await?; + + let vault_base = Keypair::new(); + + let vault_pubkey = + Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; + let vault_delegation_list = + VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; + + let lrt_mint = Keypair::new(); + let vault_admin = Keypair::new(); + let token_mint = Keypair::new(); + + self._airdrop(&vault_admin.pubkey(), 1.0).await?; + self._create_token_mint(&token_mint).await?; + + self.initialize_vault( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_pubkey, + &vault_delegation_list, + &lrt_mint, + &token_mint, + &vault_admin, + &vault_base, + deposit_fee_bps, + withdraw_fee_bps, + ) + .await?; + + Ok(( + config_admin, + VaultRoot { + vault_admin, + vault_pubkey, + }, + )) + } + + pub async fn vault_avs_opt_in( + &mut self, + vault_root: &VaultRoot, + avs: &Pubkey, + ) -> Result<(), BanksClientError> { + let vault_avs_ticket = VaultAvsTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + &avs, + ) + .0; + let avs_vault_ticket = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), + &avs, + &vault_root.vault_pubkey, + ) + .0; + self.add_avs( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &avs, + &avs_vault_ticket, + &vault_avs_ticket, + &vault_root.vault_admin, + &self.payer.insecure_clone(), + ) + .await?; + + Ok(()) + } + pub async fn initialize_vault( &mut self, config: &Pubkey, @@ -141,7 +444,7 @@ impl VaultProgramClient { ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[initialize_vault( &jito_vault_program::id(), &config, @@ -172,7 +475,8 @@ impl VaultProgramClient { payer: &Keypair, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + + self._process_transaction(&Transaction::new_signed_with_payer( &[jito_vault_sdk::add_avs( &jito_vault_program::id(), config, @@ -226,7 +530,7 @@ impl VaultProgramClient { payer: &Keypair, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[jito_vault_sdk::add_operator( &jito_vault_program::id(), config, @@ -244,30 +548,38 @@ impl VaultProgramClient { .await } - // pub async fn remove_operator( - // &mut self, - // config: &Pubkey, - // vault: &Pubkey, - // operator: &Pubkey, - // vault_operator_ticket: &Pubkey, - // admin: &Keypair, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::remove_operator( - // &jito_vault_program::id(), - // config, - // vault, - // operator, - // vault_operator_ticket, - // &admin.pubkey(), - // )], - // Some(&admin.pubkey()), - // &[admin], - // blockhash, - // )) - // .await - // } + pub async fn enqueue_withdraw( + &mut self, + config: &Pubkey, + vault: &Pubkey, + vault_delegation_list: &Pubkey, + vault_staker_withdraw_ticket: &Pubkey, + vault_staker_withdraw_ticket_token_account: &Pubkey, + staker: &Keypair, + staker_lrt_token_account: &Pubkey, + base: &Keypair, + amount: u64, + ) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::enqueue_withdraw( + &jito_vault_program::id(), + config, + vault, + vault_delegation_list, + vault_staker_withdraw_ticket, + vault_staker_withdraw_ticket_token_account, + &staker.pubkey(), + staker_lrt_token_account, + &base.pubkey(), + amount, + )], + Some(&staker.pubkey()), + &[staker, base], + blockhash, + )) + .await + } pub async fn add_delegation( &mut self, @@ -281,7 +593,7 @@ impl VaultProgramClient { amount: u64, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[add_delegation( &jito_vault_program::id(), config, @@ -344,7 +656,7 @@ impl VaultProgramClient { if let Some(signer) = mint_signer { signers.push(signer); } - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[jito_vault_sdk::mint_to( &jito_vault_program::id(), vault, @@ -464,7 +776,7 @@ impl VaultProgramClient { payer: &Keypair, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[jito_vault_sdk::add_slasher( &jito_vault_program::id(), config, @@ -495,7 +807,7 @@ impl VaultProgramClient { payer: &Keypair, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[ jito_vault_sdk::initialize_vault_avs_slasher_operator_ticket( &jito_vault_program::id(), @@ -538,7 +850,7 @@ impl VaultProgramClient { amount: u64, ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - self.process_transaction(&Transaction::new_signed_with_payer( + self._process_transaction(&Transaction::new_signed_with_payer( &[jito_vault_sdk::slash( &jito_vault_program::id(), config, @@ -567,7 +879,7 @@ impl VaultProgramClient { .await } - pub async fn process_transaction(&mut self, tx: &Transaction) -> Result<(), BanksClientError> { + async fn _process_transaction(&mut self, tx: &Transaction) -> Result<(), BanksClientError> { self.banks_client .process_transaction_with_preflight_and_commitment( tx.clone(), @@ -575,4 +887,124 @@ impl VaultProgramClient { ) .await } + + pub async fn _airdrop(&mut self, to: &Pubkey, sol: f64) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[transfer(&self.payer.pubkey(), to, sol_to_lamports(sol))], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await + } + + pub async fn get_token_account( + &mut self, + token_account: &Pubkey, + ) -> Result { + let account = self + .banks_client + .get_account(*token_account) + .await? + .unwrap(); + Ok(Account::unpack(&account.data).unwrap()) + } + + /// Mints tokens to an ATA owned by the `to` address + pub async fn mint_spl_to( + &mut self, + mint: &Pubkey, + to: &Pubkey, + amount: u64, + ) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[ + create_associated_token_account_idempotent( + &self.payer.pubkey(), + to, + mint, + &spl_token::id(), + ), + spl_token::instruction::mint_to( + &spl_token::id(), + mint, + &get_associated_token_address(to, mint), + &self.payer.pubkey(), + &[], + amount, + ) + .unwrap(), + ], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await + } + + async fn _create_token_mint(&mut self, mint: &Keypair) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + let rent: Rent = self.banks_client.get_sysvar().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[ + create_account( + &self.payer.pubkey(), + &mint.pubkey(), + rent.minimum_balance(Mint::LEN), + Mint::LEN as u64, + &spl_token::id(), + ), + initialize_mint2( + &spl_token::id(), + &mint.pubkey(), + &self.payer.pubkey(), + None, + 9, + ) + .unwrap(), + ], + Some(&self.payer.pubkey()), + &[&self.payer, mint], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await + } + + pub async fn create_ata( + &mut self, + mint: &Pubkey, + owner: &Pubkey, + ) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[create_associated_token_account_idempotent( + &self.payer.pubkey(), + owner, + mint, + &spl_token::id(), + )], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await + } } diff --git a/integration_tests/tests/vault/add_avs.rs b/integration_tests/tests/vault/add_avs.rs index d54f8c49..4808440f 100644 --- a/integration_tests/tests/vault/add_avs.rs +++ b/integration_tests/tests/vault/add_avs.rs @@ -1,128 +1,65 @@ -use jito_restaking_core::{ - avs::Avs, avs_vault_ticket::AvsVaultTicket, config::Config as RestakingConfig, -}; -use jito_vault_core::{ - config::Config as VaultConfig, vault::Vault, vault_avs_ticket::VaultAvsTicket, - vault_delegation_list::VaultDelegationList, -}; +use crate::fixtures::{fixture::TestBuilder, restaking_client::AvsRoot}; +use jito_restaking_core::{avs::Avs, config::Config}; +use log::info; +use solana_program::clock::Clock; +use solana_program::sysvar::SysvarId; use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; +use std::time::Duration; +use tokio::time::sleep; #[tokio::test] async fn test_add_avs_ok() { - let mut fixture = TestBuilder::new().await; + let fixture = TestBuilder::new().await; let mut restaking_program_client = fixture.restaking_program_client(); - let mut vault_program_client = fixture.vault_program_client(); - - let backing_token_mint = Keypair::new(); - fixture - .create_token_mint(&backing_token_mint) - .await - .unwrap(); - - let vault_config_pubkey = VaultConfig::find_program_address(&jito_vault_program::id()).0; - let vault_config_admin = Keypair::new(); - - fixture - .transfer(&vault_config_admin.pubkey(), 1.0) - .await - .unwrap(); - - vault_program_client - .initialize_config(&vault_config_pubkey, &vault_config_admin) - .await - .unwrap(); - - // create vault - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - let vault_delegation_list = - VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; - let lrt_mint = Keypair::new(); - let vault_admin = Keypair::new(); + let mut fixture = TestBuilder::new().await; - fixture.transfer(&vault_admin.pubkey(), 1.0).await.unwrap(); + let mut vault_program_client = fixture.vault_program_client(); - vault_program_client - .initialize_vault( - &vault_config_pubkey, - &vault_pubkey, - &vault_delegation_list, - &lrt_mint, - &backing_token_mint, - &vault_admin, - &vault_base, - 100, - 100, - ) - .await - .unwrap(); + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - let restaking_config_pubkey = - RestakingConfig::find_program_address(&jito_restaking_program::id()).0; - let restaking_config_admin = Keypair::new(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - fixture - .transfer(&restaking_config_admin.pubkey(), 1.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&restaking_config_pubkey, &restaking_config_admin) - .await - .unwrap(); + // let avs_root = restaking_program_client.setup_avs().await.unwrap(); // create AVS + add AVS vault let avs_admin = Keypair::new(); let avs_base = Keypair::new(); fixture.transfer(&avs_admin.pubkey(), 1.0).await.unwrap(); + sleep(Duration::from_secs(1)).await; let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client - .initialize_avs(&restaking_config_pubkey, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - let avs_vault_ticket = AvsVaultTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - ) - .0; - restaking_program_client - .avs_add_vault( - &restaking_config_pubkey, + .initialize_avs( + &Config::find_program_address(&jito_restaking_program::id()).0, &avs_pubkey, - &vault_pubkey, - &avs_vault_ticket, - &avs_admin, &avs_admin, + &avs_base, ) .await .unwrap(); - let vault_avs_ticket = - VaultAvsTicket::find_program_address(&jito_vault_program::id(), &vault_pubkey, &avs_pubkey) - .0; - vault_program_client - .add_avs( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &avs_vault_ticket, - &vault_avs_ticket, - &vault_admin, - &vault_admin, - ) - .await - .unwrap(); - - let vault_avs_ticket_account = vault_program_client - .get_vault_avs_ticket(&vault_pubkey, &avs_pubkey) - .await - .unwrap(); - assert_eq!(vault_avs_ticket_account.vault(), vault_pubkey); - assert_eq!(vault_avs_ticket_account.avs(), avs_pubkey); - assert_eq!(vault_avs_ticket_account.index(), 0); - assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); + // let avs_root = AvsRoot { + // avs_pubkey, + // avs_admin, + // }; + // + // restaking_program_client + // .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + // .await + // .unwrap(); + // + // vault_program_client + // .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + // .await + // .unwrap(); + // + // let vault_avs_ticket_account = vault_program_client + // .get_vault_avs_ticket(&vault_root.vault_pubkey, &avs_root.avs_pubkey) + // .await + // .unwrap(); + // assert_eq!(vault_avs_ticket_account.vault(), vault_root.vault_pubkey); + // assert_eq!(vault_avs_ticket_account.avs(), avs_root.avs_pubkey); + // assert_eq!(vault_avs_ticket_account.index(), 0); + // assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); } diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs new file mode 100644 index 00000000..f7c0794e --- /dev/null +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -0,0 +1,30 @@ +#[tokio::test] +async fn test_enqueue_withdrawal_success() { + // let fixture = TestBuilder::new().await; + // + // let mut vault_program_client = fixture.vault_program_client(); + + // let accounts = prepare_accounts(0, 0, 0); + // + // write_enqueue_withdraw_accounts(&accounts, &mut fixture).await; + + // fixture + // .transfer(&accounts.staker.pubkey(), 1.0) + // .await + // .unwrap(); + // + // vault_program_client + // .enqueue_withdraw( + // &accounts.config_pubkey, + // &accounts.vault_pubkey, + // &accounts.vault_delegation_list_pubkey, + // &accounts.vault_staker_withdraw_ticket, + // &accounts.vault_staker_withdraw_ticket_token_account, + // &accounts.staker, + // &accounts.staker_lrt_token_account, + // &accounts.base, + // 1000, + // ) + // .await + // .unwrap(); +} diff --git a/integration_tests/tests/vault/initialize_config.rs b/integration_tests/tests/vault/initialize_config.rs index 88f2af11..522637cf 100644 --- a/integration_tests/tests/vault/initialize_config.rs +++ b/integration_tests/tests/vault/initialize_config.rs @@ -5,21 +5,13 @@ use crate::fixtures::fixture::TestBuilder; #[tokio::test] async fn test_initialize_config_ok() { - let mut fixture = TestBuilder::new().await; + let fixture = TestBuilder::new().await; let mut vault_program_client = fixture.vault_program_client(); - let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; - let config_admin = Keypair::new(); - - fixture.transfer(&config_admin.pubkey(), 1.0).await.unwrap(); - - vault_program_client - .initialize_config(&config_pubkey, &config_admin) - .await - .unwrap(); + let config_admin = vault_program_client.setup_config().await.unwrap(); let config = vault_program_client - .get_config(&config_pubkey) + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) .await .unwrap(); diff --git a/integration_tests/tests/vault/initialize_vault.rs b/integration_tests/tests/vault/initialize_vault.rs index 0b964fc2..c366f3d4 100644 --- a/integration_tests/tests/vault/initialize_vault.rs +++ b/integration_tests/tests/vault/initialize_vault.rs @@ -1,58 +1,22 @@ -use jito_vault_core::{config::Config, vault::Vault, vault_delegation_list::VaultDelegationList}; -use solana_sdk::signature::{Keypair, Signer}; +use solana_sdk::signature::Signer; -use crate::fixtures::fixture::TestBuilder; +use crate::fixtures::{fixture::TestBuilder, vault_client::VaultRoot}; #[tokio::test] async fn test_initialize_vault_ok() { - let mut fixture = TestBuilder::new().await; - let mut vault_program_client = fixture.vault_program_client(); - - let backing_token_mint = Keypair::new(); - fixture - .create_token_mint(&backing_token_mint) - .await - .unwrap(); - - let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; - let config_admin = Keypair::new(); - - fixture.transfer(&config_admin.pubkey(), 1.0).await.unwrap(); + let fixture = TestBuilder::new().await; - vault_program_client - .initialize_config(&config_pubkey, &config_admin) - .await - .unwrap(); - - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - let vault_delegation_list = - VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; - let lrt_mint = Keypair::new(); - let vault_admin = Keypair::new(); - - fixture.transfer(&vault_admin.pubkey(), 1.0).await.unwrap(); + let mut vault_program_client = fixture.vault_program_client(); - vault_program_client - .initialize_vault( - &config_pubkey, - &vault_pubkey, - &vault_delegation_list, - &lrt_mint, - &backing_token_mint, - &vault_admin, - &vault_base, - 99, - 100, - ) - .await - .unwrap(); + let ( + _config_admin, + VaultRoot { + vault_pubkey, + vault_admin, + }, + ) = vault_program_client.setup_vault(99, 100).await.unwrap(); let vault = vault_program_client.get_vault(&vault_pubkey).await.unwrap(); - assert_eq!(vault.base(), vault_base.pubkey()); - assert_eq!(vault.lrt_mint(), lrt_mint.pubkey()); - assert_eq!(vault.supported_mint(), backing_token_mint.pubkey()); assert_eq!(vault.admin(), vault_admin.pubkey()); assert_eq!(vault.delegation_admin(), vault_admin.pubkey()); assert_eq!(vault.operator_admin(), vault_admin.pubkey()); diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index 59d08b5b..93af001b 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -1,6 +1,7 @@ mod add_avs; mod add_operator; mod add_slasher; +mod enqueue_withdrawal; mod initialize_config; mod initialize_vault; mod mint_to; diff --git a/vault_core/src/vault.rs b/vault_core/src/vault.rs index ca0a8ba8..b8ede6a4 100644 --- a/vault_core/src/vault.rs +++ b/vault_core/src/vault.rs @@ -75,7 +75,7 @@ impl Vault { lrt_mint: Pubkey, supported_mint: Pubkey, admin: Pubkey, - lrt_index: u64, + vault_index: u64, base: Pubkey, deposit_fee_bps: u16, withdrawal_fee_bps: u16, @@ -94,7 +94,7 @@ impl Vault { fee_owner: admin, mint_burn_authority: Pubkey::default(), capacity: u64::MAX, - vault_index: lrt_index, + vault_index, lrt_supply: 0, tokens_deposited: 0, deposit_fee_bps, diff --git a/vault_program/src/add_avs.rs b/vault_program/src/add_avs.rs index c657fd6b..93efc8a7 100644 --- a/vault_program/src/add_avs.rs +++ b/vault_program/src/add_avs.rs @@ -129,13 +129,16 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; + msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; + msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; + msg!("c"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -143,13 +146,18 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; + msg!("d"); let vault_avs_ticket = EmptyAccount::sanitize(next_account_info(&mut accounts_iter)?, true)?; + msg!("e"); let admin = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; + msg!("f"); let payer = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, true)?; + msg!("g"); let system_program = SanitizedSystemProgram::sanitize(next_account_info(&mut accounts_iter)?)?; + msg!("h"); Ok(SanitizedAccounts { vault, diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdraw.rs similarity index 97% rename from vault_program/src/enqueue_withdrawal.rs rename to vault_program/src/enqueue_withdraw.rs index 85e03c18..71f60650 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdraw.rs @@ -33,7 +33,7 @@ use spl_token::instruction::transfer; /// ratio increases due to rewards. However, if the vault has excess collateral that isn't staked, the vault /// can withdraw that excess and return it to the staker. If there's no excess, they can withdraw the /// amount that was set aside for withdraw. -pub fn process_enqueue_withdrawal( +pub fn process_enqueue_withdraw( program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, @@ -210,29 +210,39 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; + msg!("b"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(accounts_iter)?, true, vault.account().key, )?; + msg!("c"); let vault_staker_withdraw_ticket = EmptyAccount::sanitize(next_account_info(accounts_iter)?, true)?; + msg!("d"); let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), vault_staker_withdraw_ticket.account().key, )?; + msg!("e"); let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; + msg!("f"); let staker_lrt_token_account = SanitizedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), staker.account().key, )?; + msg!("g"); let base = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, false)?; + msg!("h"); let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + msg!("i"); let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; + msg!("j"); Ok(SanitizedAccounts { config, diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index 10830486..3605faa2 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -5,7 +5,7 @@ mod add_slasher; mod burn; mod burn_withdraw_ticket; mod create_token_metadata; -mod enqueue_withdrawal; +mod enqueue_withdraw; mod initialize_config; mod initialize_vault; mod initialize_vault_avs_slasher_operator_ticket; @@ -36,7 +36,7 @@ use crate::{ add_operator::process_vault_add_operator, add_slasher::process_add_slasher, burn::process_burn, burn_withdraw_ticket::process_burn_withdraw_ticket, create_token_metadata::process_create_token_metadata, - enqueue_withdrawal::process_enqueue_withdrawal, initialize_config::process_initialize_config, + enqueue_withdraw::process_enqueue_withdraw, initialize_config::process_initialize_config, initialize_vault::process_initialize_vault, initialize_vault_avs_slasher_operator_ticket::process_initialize_vault_avs_slasher_operator_ticket, initialize_vault_with_mint::process_initialize_vault_with_mint, mint_to::process_mint, @@ -125,8 +125,8 @@ pub fn process_instruction( process_burn(program_id, accounts, amount) } VaultInstruction::EnqueueWithdraw { amount } => { - msg!("Instruction: EnqueueWithdrawal"); - process_enqueue_withdrawal(program_id, accounts, amount) + msg!("Instruction: EnqueueWithdraw"); + process_enqueue_withdraw(program_id, accounts, amount) } VaultInstruction::BurnWithdrawTicket => { msg!("Instruction: BurnWithdrawTicket"); diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index f04e4583..db64a8dd 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -441,16 +441,6 @@ pub fn burn(program_id: &Pubkey, amount: u64) -> Instruction { } } -pub fn enqueue_withdrawal(program_id: &Pubkey, amount: u64) -> Instruction { - Instruction { - program_id: *program_id, - accounts: vec![], - data: VaultInstruction::EnqueueWithdraw { amount } - .try_to_vec() - .unwrap(), - } -} - pub fn set_deposit_capacity( program_id: &Pubkey, vault: &Pubkey, @@ -736,3 +726,37 @@ pub fn slash( data: VaultInstruction::Slash { amount }.try_to_vec().unwrap(), } } + +#[allow(clippy::too_many_arguments)] +pub fn enqueue_withdraw( + program_id: &Pubkey, + config: &Pubkey, + vault: &Pubkey, + vault_delegation_list: &Pubkey, + vault_staker_withdraw_ticket: &Pubkey, + vault_staker_withdraw_ticket_token_account: &Pubkey, + staker: &Pubkey, + staker_lrt_token_account: &Pubkey, + base: &Pubkey, + amount: u64, +) -> Instruction { + let accounts = vec![ + AccountMeta::new_readonly(*config, false), + AccountMeta::new(*vault, false), + AccountMeta::new(*vault_delegation_list, false), + AccountMeta::new(*vault_staker_withdraw_ticket, false), + AccountMeta::new(*vault_staker_withdraw_ticket_token_account, false), + AccountMeta::new(*staker, true), + AccountMeta::new(*staker_lrt_token_account, false), + AccountMeta::new_readonly(*base, true), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::EnqueueWithdraw { amount } + .try_to_vec() + .unwrap(), + } +} From e5d16e8492c98183e5f8a03478b24f6a6d2cc57f Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 23:16:40 -0500 Subject: [PATCH 04/22] cleanup tests --- integration_tests/tests/fixtures/fixture.rs | 18 +- .../tests/fixtures/restaking_client.rs | 146 +++++++ .../tests/fixtures/vault_client.rs | 266 ++++++++++- integration_tests/tests/vault/add_avs.rs | 63 +-- integration_tests/tests/vault/add_operator.rs | 129 +----- integration_tests/tests/vault/add_slasher.rs | 155 +------ .../tests/vault/initialize_config.rs | 2 +- integration_tests/tests/vault/slash.rs | 412 ++++-------------- vault_program/src/slash.rs | 19 +- 9 files changed, 544 insertions(+), 666 deletions(-) diff --git a/integration_tests/tests/fixtures/fixture.rs b/integration_tests/tests/fixtures/fixture.rs index 9ba2419f..fec69b40 100644 --- a/integration_tests/tests/fixtures/fixture.rs +++ b/integration_tests/tests/fixtures/fixture.rs @@ -7,9 +7,7 @@ use solana_program::{ rent::Rent, system_instruction::{create_account, transfer}, }; -use solana_program_test::{ - processor, BanksClient, BanksClientError, ProgramTest, ProgramTestContext, -}; +use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; use solana_sdk::{ account::AccountSharedData, commitment_config::CommitmentLevel, @@ -24,7 +22,6 @@ use spl_token::{ state::{Account, Mint}, }; use std::fmt::{Debug, Formatter}; -use std::sync::Arc; use crate::fixtures::{restaking_client::RestakingProgramClient, vault_client::VaultProgramClient}; @@ -75,19 +72,6 @@ impl TestBuilder { Ok(()) } - pub async fn get_account( - &mut self, - pubkey: &Pubkey, - ) -> Result { - let account = self - .context - .banks_client - .get_account_with_commitment(*pubkey, CommitmentLevel::Processed) - .await? - .unwrap(); - Ok(account) - } - pub async fn store_account( &mut self, pubkey: &Pubkey, diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs index 69c9c27c..3b8bee7a 100644 --- a/integration_tests/tests/fixtures/restaking_client.rs +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -9,6 +9,7 @@ use jito_restaking_sdk::{ avs_add_operator, avs_add_vault, avs_add_vault_slasher, initialize_avs, initialize_config, initialize_operator, operator_add_avs, operator_add_vault, }; +use log::info; use solana_program::{native_token::sol_to_lamports, pubkey::Pubkey, system_instruction::transfer}; use solana_program_test::{BanksClient, BanksClientError}; use solana_sdk::{ @@ -22,6 +23,11 @@ pub struct AvsRoot { pub avs_admin: Keypair, } +pub struct OperatorRoot { + pub operator_pubkey: Pubkey, + pub operator_admin: Keypair, +} + pub struct RestakingProgramClient { banks_client: BanksClient, payer: Keypair, @@ -141,6 +147,52 @@ impl RestakingProgramClient { Ok(restaking_config_admin) } + pub async fn setup_operator(&mut self) -> Result { + // create operator + add operator vault + let operator_base = Keypair::new(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + let operator_admin = Keypair::new(); + self._airdrop(&operator_admin.pubkey(), 1.0).await?; + self.initialize_operator( + &Config::find_program_address(&jito_restaking_program::id()).0, + &operator_pubkey, + &operator_admin, + &operator_base, + ) + .await + .unwrap(); + Ok(OperatorRoot { + operator_pubkey, + operator_admin, + }) + } + + pub async fn operator_vault_opt_in( + &mut self, + operator_root: &OperatorRoot, + vault_pubkey: &Pubkey, + ) -> Result<(), BanksClientError> { + let operator_vault_ticket = OperatorVaultTicket::find_program_address( + &jito_restaking_program::id(), + &operator_root.operator_pubkey, + &vault_pubkey, + ) + .0; + self.operator_add_vault( + &Config::find_program_address(&jito_restaking_program::id()).0, + &operator_root.operator_pubkey, + &vault_pubkey, + &operator_vault_ticket, + &operator_root.operator_admin, + &operator_root.operator_admin, + ) + .await?; + + Ok(()) + } + pub async fn initialize_config( &mut self, config: &Pubkey, @@ -207,6 +259,94 @@ impl RestakingProgramClient { .await } + pub async fn avs_operator_opt_in( + &mut self, + avs_root: &AvsRoot, + operator: &Pubkey, + ) -> Result<(), BanksClientError> { + let avs_operator_ticket = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), + &avs_root.avs_pubkey, + operator, + ) + .0; + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), + operator, + &avs_root.avs_pubkey, + ) + .0; + + self.avs_add_operator( + &Config::find_program_address(&jito_restaking_program::id()).0, + &avs_root.avs_pubkey, + operator, + &avs_operator_ticket, + &operator_avs_ticket, + &avs_root.avs_admin, + &self.payer.insecure_clone(), + ) + .await + } + + pub async fn operator_avs_opt_in( + &mut self, + operator_root: &OperatorRoot, + avs: &Pubkey, + ) -> Result<(), BanksClientError> { + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), + &operator_root.operator_pubkey, + avs, + ) + .0; + + self.operator_add_avs( + &Config::find_program_address(&jito_restaking_program::id()).0, + &operator_root.operator_pubkey, + avs, + &operator_avs_ticket, + &operator_root.operator_admin, + &operator_root.operator_admin, + ) + .await + } + + pub async fn avs_vault_slasher_opt_in( + &mut self, + avs_root: &AvsRoot, + vault: &Pubkey, + slasher: &Pubkey, + max_slash_amount: u64, + ) -> Result<(), BanksClientError> { + let avs_vault_ticket = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), + &avs_root.avs_pubkey, + vault, + ) + .0; + let avs_slasher_ticket = AvsVaultSlasherTicket::find_program_address( + &jito_restaking_program::id(), + &avs_root.avs_pubkey, + vault, + slasher, + ) + .0; + + self.avs_add_vault_slasher( + &Config::find_program_address(&jito_restaking_program::id()).0, + &avs_root.avs_pubkey, + vault, + slasher, + &avs_vault_ticket, + &avs_slasher_ticket, + &avs_root.avs_admin, + &self.payer.insecure_clone(), + max_slash_amount, + ) + .await + } + pub async fn initialize_avs( &mut self, config: &Pubkey, @@ -216,6 +356,12 @@ impl RestakingProgramClient { ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; + let admin_account = self + .banks_client + .get_account_with_commitment(avs_admin.pubkey(), CommitmentLevel::Processed) + .await + .unwrap(); + self.process_transaction(&Transaction::new_signed_with_payer( &[initialize_avs( &jito_restaking_program::id(), diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 58d1f315..d4053caf 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -1,5 +1,9 @@ use borsh::BorshDeserialize; +use jito_restaking_core::avs_operator_ticket::AvsOperatorTicket; +use jito_restaking_core::avs_vault_slasher_ticket::AvsVaultSlasherTicket; use jito_restaking_core::avs_vault_ticket::AvsVaultTicket; +use jito_restaking_core::operator_avs_ticket::OperatorAvsTicket; +use jito_restaking_core::operator_vault_ticket::OperatorVaultTicket; use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, @@ -227,18 +231,6 @@ impl VaultProgramClient { } } - pub async fn get_account( - &mut self, - pubkey: &Pubkey, - ) -> Result { - let account = self - .banks_client - .get_account_with_commitment(*pubkey, CommitmentLevel::Processed) - .await? - .unwrap(); - Ok(account) - } - pub async fn get_config(&mut self, account: &Pubkey) -> Result { let account = self.banks_client.get_account(*account).await?.unwrap(); Ok(Config::deserialize(&mut account.data.as_slice())?) @@ -390,6 +382,12 @@ impl VaultProgramClient { ) .await?; + // for holding the backed asset in the vault + self.create_ata(&token_mint.pubkey(), &vault_pubkey).await?; + // for holding fees + self.create_ata(&lrt_mint.pubkey(), &vault_admin.pubkey()) + .await?; + Ok(( config_admin, VaultRoot { @@ -430,6 +428,250 @@ impl VaultProgramClient { Ok(()) } + pub async fn setup_vault_avs_slasher_operator_ticket( + &mut self, + vault_root: &VaultRoot, + avs_pubkey: &Pubkey, + slasher: &Pubkey, + operator_pubkey: &Pubkey, + ) -> Result<(), BanksClientError> { + let vault_avs_slasher_ticket = VaultAvsSlasherTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + avs_pubkey, + slasher, + ) + .0; + let vault_avs_slasher_operator_ticket = + VaultAvsSlasherOperatorTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + avs_pubkey, + slasher, + operator_pubkey, + 0, // TODO (LB): fix this + ) + .0; + self.initialize_vault_avs_slasher_operator_ticket( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &avs_pubkey, + &slasher, + &operator_pubkey, + &vault_avs_slasher_ticket, + &vault_avs_slasher_operator_ticket, + &self.payer.insecure_clone(), + ) + .await + .unwrap(); + + Ok(()) + } + + pub async fn do_slash( + &mut self, + vault_root: &VaultRoot, + avs_pubkey: &Pubkey, + slasher: &Keypair, + operator_pubkey: &Pubkey, + amount: u64, + ) -> Result<(), BanksClientError> { + let avs_operator_ticket_pubkey = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), + avs_pubkey, + operator_pubkey, + ) + .0; + let operator_avs_ticket_pubkey = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), + operator_pubkey, + &avs_pubkey, + ) + .0; + let avs_vault_ticket_pubkey = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), + avs_pubkey, + &vault_root.vault_pubkey, + ) + .0; + let operator_vault_ticket_pubkey = OperatorVaultTicket::find_program_address( + &jito_restaking_program::id(), + operator_pubkey, + &vault_root.vault_pubkey, + ) + .0; + let vault_avs_ticket_pubkey = VaultAvsTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + avs_pubkey, + ) + .0; + let vault_operator_ticket = VaultOperatorTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + operator_pubkey, + ) + .0; + let avs_slasher_ticket_pubkey = AvsVaultSlasherTicket::find_program_address( + &jito_restaking_program::id(), + avs_pubkey, + &vault_root.vault_pubkey, + &slasher.pubkey(), + ) + .0; + let vault_slasher_ticket_pubkey = VaultAvsSlasherTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + avs_pubkey, + &slasher.pubkey(), + ) + .0; + let vault_delegate_list_pubkey = VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0; + let vault_avs_slasher_operator_ticket = + VaultAvsSlasherOperatorTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + avs_pubkey, + &slasher.pubkey(), + operator_pubkey, + 0, // TODO (LB): fix this + ) + .0; + + let vault = self.get_vault(&vault_root.vault_pubkey).await.unwrap(); + let vault_token_account = + get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()); + let slasher_token_account = + get_associated_token_address(&slasher.pubkey(), &vault.supported_mint()); + + self.slash( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &avs_pubkey, + &operator_pubkey, + slasher, + &avs_operator_ticket_pubkey, + &operator_avs_ticket_pubkey, + &avs_vault_ticket_pubkey, + &operator_vault_ticket_pubkey, + &vault_avs_ticket_pubkey, + &vault_operator_ticket, + &avs_slasher_ticket_pubkey, + &vault_slasher_ticket_pubkey, + &vault_delegate_list_pubkey, + &vault_avs_slasher_operator_ticket, + &vault_token_account, + &slasher_token_account, + amount, + ) + .await + .unwrap(); + + Ok(()) + } + + pub async fn vault_operator_opt_in( + &mut self, + vault_root: &VaultRoot, + operator_pubkey: &Pubkey, + ) -> Result<(), BanksClientError> { + let vault_operator_ticket = VaultOperatorTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + &operator_pubkey, + ) + .0; + let operator_vault_ticket = OperatorVaultTicket::find_program_address( + &jito_restaking_program::id(), + &operator_pubkey, + &vault_root.vault_pubkey, + ) + .0; + self.add_operator( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &operator_pubkey, + &operator_vault_ticket, + &vault_operator_ticket, + &vault_root.vault_admin, + &vault_root.vault_admin, + ) + .await?; + + Ok(()) + } + + pub async fn vault_avs_vault_slasher_opt_in( + &mut self, + vault_root: &VaultRoot, + avs_pubkey: &Pubkey, + slasher: &Pubkey, + ) -> Result<(), BanksClientError> { + let vault_slasher_ticket_pubkey = VaultAvsSlasherTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + &avs_pubkey, + slasher, + ) + .0; + let avs_slasher_ticket_pubkey = AvsVaultSlasherTicket::find_program_address( + &jito_restaking_program::id(), + &avs_pubkey, + &vault_root.vault_pubkey, + slasher, + ) + .0; + + self.add_slasher( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &avs_pubkey, + slasher, + &avs_slasher_ticket_pubkey, + &vault_slasher_ticket_pubkey, + &vault_root.vault_admin, + &vault_root.vault_admin, + ) + .await + .unwrap(); + + Ok(()) + } + + pub async fn delegate( + &mut self, + vault_root: &VaultRoot, + operator: &Pubkey, + amount: u64, + ) -> Result<(), BanksClientError> { + self.add_delegation( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + operator, + &VaultOperatorTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + operator, + ) + .0, + &VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0, + &vault_root.vault_admin, + &vault_root.vault_admin, + amount, + ) + .await?; + + Ok(()) + } + pub async fn initialize_vault( &mut self, config: &Pubkey, diff --git a/integration_tests/tests/vault/add_avs.rs b/integration_tests/tests/vault/add_avs.rs index 4808440f..3811de06 100644 --- a/integration_tests/tests/vault/add_avs.rs +++ b/integration_tests/tests/vault/add_avs.rs @@ -1,65 +1,34 @@ -use crate::fixtures::{fixture::TestBuilder, restaking_client::AvsRoot}; -use jito_restaking_core::{avs::Avs, config::Config}; -use log::info; -use solana_program::clock::Clock; -use solana_program::sysvar::SysvarId; -use solana_sdk::signature::{Keypair, Signer}; -use std::time::Duration; -use tokio::time::sleep; +use crate::fixtures::fixture::TestBuilder; #[tokio::test] async fn test_add_avs_ok() { let fixture = TestBuilder::new().await; let mut restaking_program_client = fixture.restaking_program_client(); - let mut fixture = TestBuilder::new().await; - let mut vault_program_client = fixture.vault_program_client(); let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - // let avs_root = restaking_program_client.setup_avs().await.unwrap(); - - // create AVS + add AVS vault - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 1.0).await.unwrap(); - sleep(Duration::from_secs(1)).await; - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + let avs_root = restaking_program_client.setup_avs().await.unwrap(); restaking_program_client - .initialize_avs( - &Config::find_program_address(&jito_restaking_program::id()).0, - &avs_pubkey, - &avs_admin, - &avs_base, - ) + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) .await .unwrap(); - // let avs_root = AvsRoot { - // avs_pubkey, - // avs_admin, - // }; - // - // restaking_program_client - // .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - // .await - // .unwrap(); - // - // vault_program_client - // .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - // .await - // .unwrap(); - // - // let vault_avs_ticket_account = vault_program_client - // .get_vault_avs_ticket(&vault_root.vault_pubkey, &avs_root.avs_pubkey) - // .await - // .unwrap(); - // assert_eq!(vault_avs_ticket_account.vault(), vault_root.vault_pubkey); - // assert_eq!(vault_avs_ticket_account.avs(), avs_root.avs_pubkey); - // assert_eq!(vault_avs_ticket_account.index(), 0); - // assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + + let vault_avs_ticket_account = vault_program_client + .get_vault_avs_ticket(&vault_root.vault_pubkey, &avs_root.avs_pubkey) + .await + .unwrap(); + assert_eq!(vault_avs_ticket_account.vault(), vault_root.vault_pubkey); + assert_eq!(vault_avs_ticket_account.avs(), avs_root.avs_pubkey); + assert_eq!(vault_avs_ticket_account.index(), 0); + assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); } diff --git a/integration_tests/tests/vault/add_operator.rs b/integration_tests/tests/vault/add_operator.rs index b4ff89b3..60147478 100644 --- a/integration_tests/tests/vault/add_operator.rs +++ b/integration_tests/tests/vault/add_operator.rs @@ -1,142 +1,37 @@ -use jito_restaking_core::{ - config::Config as RestakingConfig, operator::Operator, - operator_vault_ticket::OperatorVaultTicket, -}; -use jito_vault_core::{ - config::Config as VaultConfig, vault::Vault, vault_delegation_list::VaultDelegationList, - vault_operator_ticket::VaultOperatorTicket, -}; -use solana_sdk::signature::{Keypair, Signer}; - use crate::fixtures::fixture::TestBuilder; #[tokio::test] async fn test_add_operator_ok() { - let mut fixture = TestBuilder::new().await; + let fixture = TestBuilder::new().await; let mut restaking_program_client = fixture.restaking_program_client(); let mut vault_program_client = fixture.vault_program_client(); - let backing_token_mint = Keypair::new(); - fixture - .create_token_mint(&backing_token_mint) - .await - .unwrap(); - - let vault_config_pubkey = VaultConfig::find_program_address(&jito_vault_program::id()).0; - let vault_config_admin = Keypair::new(); - - fixture - .transfer(&vault_config_admin.pubkey(), 1.0) - .await - .unwrap(); - - vault_program_client - .initialize_config(&vault_config_pubkey, &vault_config_admin) - .await - .unwrap(); - - // create vault - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - let vault_delegation_list = - VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; - let lrt_mint = Keypair::new(); - let vault_admin = Keypair::new(); - - fixture.transfer(&vault_admin.pubkey(), 1.0).await.unwrap(); - - vault_program_client - .initialize_vault( - &vault_config_pubkey, - &vault_pubkey, - &vault_delegation_list, - &lrt_mint, - &backing_token_mint, - &vault_admin, - &vault_base, - 100, - 100, - ) - .await - .unwrap(); - - let restaking_config_pubkey = - RestakingConfig::find_program_address(&jito_restaking_program::id()).0; - let restaking_config_admin = Keypair::new(); + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - fixture - .transfer(&restaking_config_admin.pubkey(), 1.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&restaking_config_pubkey, &restaking_config_admin) - .await - .unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - // create operator + add operator vault - let operator_base = Keypair::new(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - let operator_admin = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 1.0) - .await - .unwrap(); - restaking_program_client - .initialize_operator( - &restaking_config_pubkey, - &operator_pubkey, - &operator_admin, - &operator_base, - ) - .await - .unwrap(); + let operator_root = restaking_program_client.setup_operator().await.unwrap(); - let operator_vault_ticket = OperatorVaultTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &vault_pubkey, - ) - .0; restaking_program_client - .operator_add_vault( - &restaking_config_pubkey, - &operator_pubkey, - &vault_pubkey, - &operator_vault_ticket, - &operator_admin, - &operator_admin, - ) + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) .await .unwrap(); - let vault_operator_ticket = VaultOperatorTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &operator_pubkey, - ) - .0; vault_program_client - .add_operator( - &vault_config_pubkey, - &vault_pubkey, - &operator_pubkey, - &operator_vault_ticket, - &vault_operator_ticket, - &vault_admin, - &vault_admin, - ) + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) .await .unwrap(); let vault_operator_ticket = vault_program_client - .get_vault_operator_ticket(&vault_pubkey, &operator_pubkey) + .get_vault_operator_ticket(&vault_root.vault_pubkey, &operator_root.operator_pubkey) .await .unwrap(); - assert_eq!(vault_operator_ticket.vault(), vault_pubkey); - assert_eq!(vault_operator_ticket.operator(), operator_pubkey); + assert_eq!(vault_operator_ticket.vault(), vault_root.vault_pubkey); + assert_eq!( + vault_operator_ticket.operator(), + operator_root.operator_pubkey + ); assert_eq!(vault_operator_ticket.index(), 0); assert_eq!(vault_operator_ticket.state().slot_added(), 1); } diff --git a/integration_tests/tests/vault/add_slasher.rs b/integration_tests/tests/vault/add_slasher.rs index a85bd8a7..74b6a814 100644 --- a/integration_tests/tests/vault/add_slasher.rs +++ b/integration_tests/tests/vault/add_slasher.rs @@ -1,174 +1,51 @@ -use jito_restaking_core::{ - avs::Avs, avs_vault_slasher_ticket::AvsVaultSlasherTicket, avs_vault_ticket::AvsVaultTicket, - config::Config as RestakingConfig, -}; -use jito_vault_core::{ - config::Config as VaultConfig, vault::Vault, vault_avs_slasher_ticket::VaultAvsSlasherTicket, - vault_avs_ticket::VaultAvsTicket, vault_delegation_list::VaultDelegationList, -}; use solana_sdk::signature::{Keypair, Signer}; use crate::fixtures::fixture::TestBuilder; #[tokio::test] async fn test_add_slasher_ok() { - let mut fixture = TestBuilder::new().await; + let fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); let mut vault_program_client = fixture.vault_program_client(); - // Initialize restaking config - let config_admin = Keypair::new(); - let restaking_config = RestakingConfig::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&restaking_config, &config_admin) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&restaking_config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - // Initialize vault config - let vault_config_pubkey = VaultConfig::find_program_address(&jito_vault_program::id()).0; - let vault_config_admin = Keypair::new(); - fixture - .transfer(&vault_config_admin.pubkey(), 1.0) - .await - .unwrap(); - vault_program_client - .initialize_config(&vault_config_pubkey, &vault_config_admin) - .await - .unwrap(); - - // Initialize Vault - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - let vault_delegate_list_pubkey = - VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; - let lrt_mint = Keypair::new(); - let token_mint = Keypair::new(); - let vault_admin = Keypair::new(); - - fixture.create_token_mint(&token_mint).await.unwrap(); - fixture.transfer(&vault_admin.pubkey(), 1.0).await.unwrap(); + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - vault_program_client - .initialize_vault( - &vault_config_pubkey, - &vault_pubkey, - &vault_delegate_list_pubkey, - &lrt_mint, - &token_mint, - &vault_admin, - &vault_base, - 100, - 100, - ) - .await - .unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - let avs_vault_ticket_pubkey = AvsVaultTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - ) - .0; + let avs_root = restaking_program_client.setup_avs().await.unwrap(); - // AVS adds vault restaking_program_client - .avs_add_vault( - &restaking_config, - &avs_pubkey, - &vault_pubkey, - &avs_vault_ticket_pubkey, - &avs_admin, - &avs_admin, - ) + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) .await .unwrap(); - // vault adds avs - let vault_avs_ticket_pubkey = - VaultAvsTicket::find_program_address(&jito_vault_program::id(), &vault_pubkey, &avs_pubkey) - .0; vault_program_client - .add_avs( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &avs_vault_ticket_pubkey, - &vault_avs_ticket_pubkey, - &vault_admin, - &vault_admin, - ) + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) .await .unwrap(); - // AVS adds slasher let slasher = Keypair::new(); - let avs_slasher_ticket_pubkey = AvsVaultSlasherTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - &slasher.pubkey(), - ) - .0; - restaking_program_client - .avs_add_vault_slasher( - &restaking_config, - &avs_pubkey, - &vault_pubkey, - &slasher.pubkey(), - &avs_vault_ticket_pubkey, - &avs_slasher_ticket_pubkey, - &avs_admin, - &avs_admin, - 100, - ) + .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) .await .unwrap(); - // vault adds slasher - let vault_slasher_ticket_pubkey = VaultAvsSlasherTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - ) - .0; - vault_program_client - .add_slasher( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - &avs_slasher_ticket_pubkey, - &vault_slasher_ticket_pubkey, - &vault_admin, - &vault_admin, - ) + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) .await .unwrap(); let vault_avs_slasher = vault_program_client - .get_vault_avs_slasher_ticket(&vault_pubkey, &avs_pubkey, &slasher.pubkey()) + .get_vault_avs_slasher_ticket( + &vault_root.vault_pubkey, + &avs_root.avs_pubkey, + &slasher.pubkey(), + ) .await .unwrap(); - assert_eq!(vault_avs_slasher.vault(), vault_pubkey); - assert_eq!(vault_avs_slasher.avs(), avs_pubkey); + assert_eq!(vault_avs_slasher.vault(), vault_root.vault_pubkey); + assert_eq!(vault_avs_slasher.avs(), avs_root.avs_pubkey); assert_eq!(vault_avs_slasher.slasher(), slasher.pubkey()); assert_eq!(vault_avs_slasher.index(), 0); assert_eq!(vault_avs_slasher.max_slashable_per_epoch(), 100); diff --git a/integration_tests/tests/vault/initialize_config.rs b/integration_tests/tests/vault/initialize_config.rs index 522637cf..969dc711 100644 --- a/integration_tests/tests/vault/initialize_config.rs +++ b/integration_tests/tests/vault/initialize_config.rs @@ -1,5 +1,5 @@ use jito_vault_core::config::Config; -use solana_sdk::signature::{Keypair, Signer}; +use solana_sdk::signature::Signer; use crate::fixtures::fixture::TestBuilder; diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index 37e7e83e..ab026d43 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -1,451 +1,199 @@ -use jito_restaking_core::{ - avs::Avs, avs_operator_ticket::AvsOperatorTicket, - avs_vault_slasher_ticket::AvsVaultSlasherTicket, avs_vault_ticket::AvsVaultTicket, - config::Config as RestakingConfig, operator::Operator, operator_avs_ticket::OperatorAvsTicket, - operator_vault_ticket::OperatorVaultTicket, -}; -use jito_vault_core::{ - config::Config as VaultConfig, vault::Vault, - vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, - vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, - vault_delegation_list::VaultDelegationList, vault_operator_ticket::VaultOperatorTicket, -}; +use crate::fixtures::fixture::TestBuilder; +use jito_vault_core::vault_delegation_list::VaultDelegationList; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; -use crate::fixtures::fixture::TestBuilder; - #[tokio::test] async fn test_slash_ok() { let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); let mut vault_program_client = fixture.vault_program_client(); - // Initialize restaking config - let config_admin = Keypair::new(); - let restaking_config = RestakingConfig::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&restaking_config, &config_admin) - .await - .unwrap(); + let (_config_admin, vault_root) = vault_program_client.setup_vault(100, 100).await.unwrap(); - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&restaking_config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 1.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator( - &restaking_config, - &operator_pubkey, - &operator_admin, - &operator_base, - ) - .await - .unwrap(); + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + let operator_root = restaking_program_client.setup_operator().await.unwrap(); - // Initialize vault config - let vault_config_pubkey = VaultConfig::find_program_address(&jito_vault_program::id()).0; - let vault_config_admin = Keypair::new(); - fixture - .transfer(&vault_config_admin.pubkey(), 1.0) - .await - .unwrap(); - vault_program_client - .initialize_config(&vault_config_pubkey, &vault_config_admin) + // AVS <-> Vault + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) .await .unwrap(); - - // Initialize Vault - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - let vault_delegate_list_pubkey = - VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; - let lrt_mint = Keypair::new(); - let token_mint = Keypair::new(); - let vault_admin = Keypair::new(); - - fixture.create_token_mint(&token_mint).await.unwrap(); - fixture.transfer(&vault_admin.pubkey(), 1.0).await.unwrap(); - vault_program_client - .initialize_vault( - &vault_config_pubkey, - &vault_pubkey, - &vault_delegate_list_pubkey, - &lrt_mint, - &token_mint, - &vault_admin, - &vault_base, - 100, - 100, - ) - .await - .unwrap(); - - let avs_vault_ticket_pubkey = AvsVaultTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - ) - .0; - - // AVS adds vault - restaking_program_client - .avs_add_vault( - &restaking_config, - &avs_pubkey, - &vault_pubkey, - &avs_vault_ticket_pubkey, - &avs_admin, - &avs_admin, - ) + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) .await .unwrap(); - // Operator adds vault - let operator_vault_ticket_pubkey = OperatorVaultTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &vault_pubkey, - ) - .0; + // AVS <-> Operator + // operator needs to opt-in first restaking_program_client - .operator_add_vault( - &restaking_config, - &operator_pubkey, - &vault_pubkey, - &operator_vault_ticket_pubkey, - &operator_admin, - &operator_admin, - ) + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) .await .unwrap(); - - // operator adds avs - let operator_avs_ticket_pubkey = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; restaking_program_client - .operator_add_avs( - &restaking_config, - &operator_pubkey, - &avs_pubkey, - &operator_avs_ticket_pubkey, - &operator_admin, - &operator_admin, - ) + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) .await .unwrap(); - // avs adds operator - let avs_operator_ticket_pubkey = AvsOperatorTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &operator_pubkey, - ) - .0; + // Vault <-> Operator + // operator needs to opt-in first restaking_program_client - .avs_add_operator( - &restaking_config, - &avs_pubkey, - &operator_pubkey, - &avs_operator_ticket_pubkey, - &operator_avs_ticket_pubkey, - &avs_admin, - &avs_admin, - ) - .await - .unwrap(); - - // vault adds avs - let vault_avs_ticket_pubkey = - VaultAvsTicket::find_program_address(&jito_vault_program::id(), &vault_pubkey, &avs_pubkey) - .0; - vault_program_client - .add_avs( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &avs_vault_ticket_pubkey, - &vault_avs_ticket_pubkey, - &vault_admin, - &vault_admin, - ) + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) .await .unwrap(); - - // vault adds operator - let vault_operator_ticket = VaultOperatorTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &operator_pubkey, - ) - .0; vault_program_client - .add_operator( - &vault_config_pubkey, - &vault_pubkey, - &operator_pubkey, - &operator_vault_ticket_pubkey, - &vault_operator_ticket, - &vault_admin, - &vault_admin, - ) + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) .await .unwrap(); - // AVS adds slasher + // AVS + vault configures slasher let slasher = Keypair::new(); fixture.transfer(&slasher.pubkey(), 1.0).await.unwrap(); - let avs_slasher_ticket_pubkey = AvsVaultSlasherTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - &slasher.pubkey(), - ) - .0; restaking_program_client - .avs_add_vault_slasher( - &restaking_config, - &avs_pubkey, - &vault_pubkey, - &slasher.pubkey(), - &avs_vault_ticket_pubkey, - &avs_slasher_ticket_pubkey, - &avs_admin, - &avs_admin, - 100, - ) + .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) .await .unwrap(); - - // vault adds slasher - let vault_slasher_ticket_pubkey = VaultAvsSlasherTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - ) - .0; - vault_program_client - .add_slasher( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - &avs_slasher_ticket_pubkey, - &vault_slasher_ticket_pubkey, - &vault_admin, - &vault_admin, - ) + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) .await .unwrap(); - let depositor = Keypair::new(); - fixture.transfer(&depositor.pubkey(), 1.0).await.unwrap(); - fixture - .mint_to(&token_mint.pubkey(), &depositor.pubkey(), 100_000) + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) .await .unwrap(); - let depositor_token_account = - get_associated_token_address(&depositor.pubkey(), &token_mint.pubkey()); - let depositor_lrt_token_account = - get_associated_token_address(&depositor.pubkey(), &lrt_mint.pubkey()); - let vault_fee_token_account = - get_associated_token_address(&vault_admin.pubkey(), &lrt_mint.pubkey()); - let vault_token_account = get_associated_token_address(&vault_pubkey, &token_mint.pubkey()); - - // deposit lrt receiver + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 1.0).await.unwrap(); fixture - .create_ata(&lrt_mint.pubkey(), &depositor.pubkey()) + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) .await .unwrap(); - // vault fee account - fixture - .create_ata(&lrt_mint.pubkey(), &vault_admin.pubkey()) + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) .await .unwrap(); - // vault holdings + // depositor ATA for LRT fixture - .create_ata(&token_mint.pubkey(), &vault_pubkey) + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) .await .unwrap(); vault_program_client .mint_to( - &vault_pubkey, - &lrt_mint.pubkey(), + &vault_root.vault_pubkey, + &vault.lrt_mint(), &depositor, - &depositor_token_account, - &vault_token_account, - &depositor_lrt_token_account, - &vault_fee_token_account, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), None, 100_000, ) .await .unwrap(); - let vault = vault_program_client.get_vault(&vault_pubkey).await.unwrap(); - assert_eq!(vault.lrt_supply(), 100_000); - - let fee_account = fixture - .get_token_account(&vault_fee_token_account) - .await - .unwrap(); - assert_eq!(fee_account.amount, 1_000); - - let user_account = fixture - .get_token_account(&depositor_lrt_token_account) - .await - .unwrap(); - assert_eq!(user_account.amount, 99_000); + // user has 99_000 because 100 bips deposit fee vault_program_client - .add_delegation( - &vault_config_pubkey, - &vault_pubkey, - &operator_pubkey, - &vault_operator_ticket, - &vault_delegate_list_pubkey, - &vault_admin, - &vault_admin, - 10_000, - ) + .delegate(&vault_root, &operator_root.operator_pubkey, 10_000) .await .unwrap(); let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_delegate_list_pubkey) + .get_vault_delegation_list( + &VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0, + ) .await .unwrap(); + let delegations = vault_delegation_list.delegations(); assert_eq!(delegations.len(), 1); - assert_eq!(delegations[0].operator(), operator_pubkey); + assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); assert_eq!(delegations[0].staked_amount(), 10_000); - let slasher_token_account = - get_associated_token_address(&slasher.pubkey(), &token_mint.pubkey()); fixture - .create_ata(&token_mint.pubkey(), &slasher.pubkey()) + .create_ata(&vault.supported_mint(), &slasher.pubkey()) .await .unwrap(); - - let vault_avs_slasher_ticket = VaultAvsSlasherTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - ) - .0; - - let vault_avs_slasher_operator_ticket = VaultAvsSlasherOperatorTicket::find_program_address( - &jito_vault_program::id(), - &vault_pubkey, - &avs_pubkey, - &slasher.pubkey(), - &operator_pubkey, - 0, - ) - .0; + let slasher_token_account = + get_associated_token_address(&slasher.pubkey(), &vault.supported_mint()); vault_program_client - .initialize_vault_avs_slasher_operator_ticket( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, + .setup_vault_avs_slasher_operator_ticket( + &vault_root, + &avs_root.avs_pubkey, &slasher.pubkey(), - &operator_pubkey, - &vault_avs_slasher_ticket, - &vault_avs_slasher_operator_ticket, - &vault_admin, + &operator_root.operator_pubkey, ) .await .unwrap(); vault_program_client - .slash( - &vault_config_pubkey, - &vault_pubkey, - &avs_pubkey, - &operator_pubkey, + .do_slash( + &vault_root, + &avs_root.avs_pubkey, &slasher, - &avs_operator_ticket_pubkey, - &operator_avs_ticket_pubkey, - &avs_vault_ticket_pubkey, - &operator_vault_ticket_pubkey, - &vault_avs_ticket_pubkey, - &vault_operator_ticket, - &avs_slasher_ticket_pubkey, - &vault_slasher_ticket_pubkey, - &vault_delegate_list_pubkey, - &vault_avs_slasher_operator_ticket, - &vault_token_account, - &slasher_token_account, + &operator_root.operator_pubkey, 100, ) .await .unwrap(); - let vault = vault_program_client.get_vault(&vault_pubkey).await.unwrap(); + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); assert_eq!(vault.tokens_deposited(), 99_900); let delegation_list = vault_program_client - .get_vault_delegation_list(&vault_delegate_list_pubkey) + .get_vault_delegation_list( + &VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0, + ) .await .unwrap(); let delegations = delegation_list.delegations(); assert_eq!(delegations.len(), 1); - assert_eq!(delegations[0].operator(), operator_pubkey); + assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); assert_eq!(delegations[0].staked_amount(), 9_900); let vault_avs_slasher_operator_ticket = vault_program_client .get_vault_avs_slasher_operator_ticket( - &vault_pubkey, - &avs_pubkey, + &vault_root.vault_pubkey, + &avs_root.avs_pubkey, &slasher.pubkey(), - &operator_pubkey, + &operator_root.operator_pubkey, 0, ) .await .unwrap(); assert_eq!(vault_avs_slasher_operator_ticket.slashed(), 100); assert_eq!(vault_avs_slasher_operator_ticket.epoch(), 0); - assert_eq!(vault_avs_slasher_operator_ticket.vault(), vault_pubkey); - assert_eq!(vault_avs_slasher_operator_ticket.avs(), avs_pubkey); + assert_eq!( + vault_avs_slasher_operator_ticket.vault(), + vault_root.vault_pubkey + ); + assert_eq!(vault_avs_slasher_operator_ticket.avs(), avs_root.avs_pubkey); assert_eq!( vault_avs_slasher_operator_ticket.slasher(), slasher.pubkey() ); assert_eq!( vault_avs_slasher_operator_ticket.operator(), - operator_pubkey + operator_root.operator_pubkey ); } diff --git a/vault_program/src/slash.rs b/vault_program/src/slash.rs index 0cb22fa5..af9b5407 100644 --- a/vault_program/src/slash.rs +++ b/vault_program/src/slash.rs @@ -22,6 +22,7 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, + msg, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, @@ -180,20 +181,25 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; + msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; + msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; + msg!("c"); let operator = SanitizedOperator::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; + msg!("d"); let slasher = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; + msg!("e"); let avs_operator_ticket = SanitizedAvsOperatorTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -201,6 +207,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, operator.account().key, )?; + msg!("f"); let operator_avs_ticket = SanitizedOperatorAvsTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -208,6 +215,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, avs.account().key, )?; + msg!("g"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -215,6 +223,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; + msg!("h"); let operator_vault_ticket = SanitizedOperatorVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -222,6 +231,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, vault.account().key, )?; + msg!("i"); let vault_avs_ticket = SanitizedVaultAvsTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -229,6 +239,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, avs.account().key, )?; + msg!("j"); let vault_operator_ticket = SanitizedVaultOperatorTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -236,6 +247,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, operator.account().key, )?; + msg!("k"); let avs_vault_slasher_ticket = SanitizedAvsVaultSlasherTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -244,6 +256,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, slasher.account().key, )?; + msg!("l"); let vault_avs_slasher_ticket = SanitizedVaultAvsSlasherTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -252,13 +265,14 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, slasher.account().key, )?; + msg!("m"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(&mut accounts_iter)?, true, vault.account().key, )?; - + msg!("n"); let epoch = slot.checked_div(config.config().epoch_length()).unwrap(); let vault_avs_slasher_operator_ticket = SanitizedVaultAvsSlasherOperatorTicket::sanitize( program_id, @@ -270,17 +284,20 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, epoch, )?; + msg!("o"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; + msg!("p"); let slasher_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), slasher.account().key, )?; + msg!("q"); let _token_program = SanitizedTokenProgram::sanitize(next_account_info(&mut accounts_iter)?)?; Ok(Self { From 86e6dbca66af7e0d688884945d174b5385914cd6 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 23:25:07 -0500 Subject: [PATCH 05/22] setup enqueue withdraw --- .../tests/vault/enqueue_withdrawal.rs | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index f7c0794e..d125be06 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,30 +1,53 @@ +use crate::fixtures::fixture::TestBuilder; +use solana_sdk::signature::{Keypair, Signer}; +use spl_associated_token_account::get_associated_token_address; + #[tokio::test] async fn test_enqueue_withdrawal_success() { - // let fixture = TestBuilder::new().await; - // - // let mut vault_program_client = fixture.vault_program_client(); - - // let accounts = prepare_accounts(0, 0, 0); - // - // write_enqueue_withdraw_accounts(&accounts, &mut fixture).await; - - // fixture - // .transfer(&accounts.staker.pubkey(), 1.0) - // .await - // .unwrap(); - // - // vault_program_client - // .enqueue_withdraw( - // &accounts.config_pubkey, - // &accounts.vault_pubkey, - // &accounts.vault_delegation_list_pubkey, - // &accounts.vault_staker_withdraw_ticket, - // &accounts.vault_staker_withdraw_ticket_token_account, - // &accounts.staker, - // &accounts.staker_lrt_token_account, - // &accounts.base, - // 1000, - // ) - // .await - // .unwrap(); + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + + let (vault_config_admin, vault_root) = + vault_program_client.setup_vault(100, 100).await.unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + let depositor_lrt_token_account = + get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + let depositor_ata = fixture + .get_token_account(&depositor_lrt_token_account) + .await + .unwrap(); + assert_eq!(depositor_ata.amount, 99_000); } From 5f95a954751682b76b6260ac0fae76539ffa9bbd Mon Sep 17 00:00:00 2001 From: Lucas B Date: Tue, 30 Jul 2024 23:38:55 -0500 Subject: [PATCH 06/22] getting closer --- integration_tests/Cargo.toml | 2 +- integration_tests/tests/fixtures/fixture.rs | 108 +---- .../tests/fixtures/restaking_client.rs | 8 +- .../tests/fixtures/vault_client.rs | 383 +++--------------- .../tests/vault/enqueue_withdrawal.rs | 15 +- integration_tests/tests/vault/slash.rs | 3 +- vault_program/src/add_avs.rs | 8 - vault_program/src/enqueue_withdraw.rs | 8 + 8 files changed, 93 insertions(+), 442 deletions(-) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 3ad394b2..8067bc6b 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -28,4 +28,4 @@ spl-token = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } [dependencies] -log = "0.4.21" \ No newline at end of file +log = "0.4.21" diff --git a/integration_tests/tests/fixtures/fixture.rs b/integration_tests/tests/fixtures/fixture.rs index fec69b40..4953a111 100644 --- a/integration_tests/tests/fixtures/fixture.rs +++ b/integration_tests/tests/fixtures/fixture.rs @@ -1,27 +1,14 @@ -use borsh::BorshSerialize; +use std::fmt::{Debug, Formatter}; + use solana_program::{ - clock::Clock, - native_token::sol_to_lamports, - program_pack::Pack, - pubkey::Pubkey, - rent::Rent, - system_instruction::{create_account, transfer}, + native_token::sol_to_lamports, program_pack::Pack, pubkey::Pubkey, system_instruction::transfer, }; use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; -use solana_sdk::{ - account::AccountSharedData, - commitment_config::CommitmentLevel, - signature::{Keypair, Signer}, - transaction::Transaction, -}; +use solana_sdk::{commitment_config::CommitmentLevel, signature::Signer, transaction::Transaction}; use spl_associated_token_account::{ get_associated_token_address, instruction::create_associated_token_account_idempotent, }; -use spl_token::{ - instruction::initialize_mint2, - state::{Account, Mint}, -}; -use std::fmt::{Debug, Formatter}; +use spl_token::state::Account; use crate::fixtures::{restaking_client::RestakingProgramClient, vault_client::VaultProgramClient}; @@ -53,44 +40,6 @@ impl TestBuilder { Self { context } } - pub async fn store_borsh_account( - &mut self, - pubkey: &Pubkey, - owner: &Pubkey, - data: &T, - ) -> Result<(), BanksClientError> { - let rent: Rent = self.context.banks_client.get_sysvar().await?; - - let serialized = data.try_to_vec().unwrap(); - let mut data = AccountSharedData::new( - rent.minimum_balance(serialized.len()), - serialized.len(), - owner, - ); - data.set_data_from_slice(serialized.as_slice()); - self.context.set_account(pubkey, &data); - Ok(()) - } - - pub async fn store_account( - &mut self, - pubkey: &Pubkey, - owner: &Pubkey, - data: &[u8], - ) -> Result<(), BanksClientError> { - let rent: Rent = self.context.banks_client.get_sysvar().await?; - - let serialized = data.to_vec(); - let mut data = AccountSharedData::new( - rent.minimum_balance(serialized.len()), - serialized.len(), - owner, - ); - data.set_data_from_slice(serialized.as_slice()); - self.context.set_account(pubkey, &data); - Ok(()) - } - pub async fn transfer(&mut self, to: &Pubkey, sol: f64) -> Result<(), BanksClientError> { let blockhash = self.context.banks_client.get_latest_blockhash().await?; self.context @@ -162,39 +111,6 @@ impl TestBuilder { .await } - pub async fn create_token_mint(&mut self, mint: &Keypair) -> Result<(), BanksClientError> { - let blockhash = self.context.banks_client.get_latest_blockhash().await?; - let rent: Rent = self.context.banks_client.get_sysvar().await?; - self.context - .banks_client - .process_transaction_with_preflight_and_commitment( - Transaction::new_signed_with_payer( - &[ - create_account( - &self.context.payer.pubkey(), - &mint.pubkey(), - rent.minimum_balance(Mint::LEN), - Mint::LEN as u64, - &spl_token::id(), - ), - initialize_mint2( - &spl_token::id(), - &mint.pubkey(), - &self.context.payer.pubkey(), - None, - 9, - ) - .unwrap(), - ], - Some(&self.context.payer.pubkey()), - &[&self.context.payer, mint], - blockhash, - ), - CommitmentLevel::Processed, - ) - .await - } - pub async fn create_ata( &mut self, mint: &Pubkey, @@ -220,13 +136,13 @@ impl TestBuilder { .await } - pub async fn warp_to_next_slot(&mut self) -> Result<(), BanksClientError> { - let clock: Clock = self.context.banks_client.get_sysvar().await?; - self.context - .warp_to_slot(clock.slot.checked_add(1).unwrap()) - .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; - Ok(()) - } + // pub async fn warp_to_next_slot(&mut self) -> Result<(), BanksClientError> { + // let clock: Clock = self.context.banks_client.get_sysvar().await?; + // self.context + // .warp_to_slot(clock.slot.checked_add(1).unwrap()) + // .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; + // Ok(()) + // } pub fn vault_program_client(&self) -> VaultProgramClient { VaultProgramClient::new( diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs index 3b8bee7a..11735461 100644 --- a/integration_tests/tests/fixtures/restaking_client.rs +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -9,7 +9,7 @@ use jito_restaking_sdk::{ avs_add_operator, avs_add_vault, avs_add_vault_slasher, initialize_avs, initialize_config, initialize_operator, operator_add_avs, operator_add_vault, }; -use log::info; + use solana_program::{native_token::sol_to_lamports, pubkey::Pubkey, system_instruction::transfer}; use solana_program_test::{BanksClient, BanksClientError}; use solana_sdk::{ @@ -356,12 +356,6 @@ impl RestakingProgramClient { ) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; - let admin_account = self - .banks_client - .get_account_with_commitment(avs_admin.pubkey(), CommitmentLevel::Processed) - .await - .unwrap(); - self.process_transaction(&Transaction::new_signed_with_payer( &[initialize_avs( &jito_restaking_program::id(), diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index d4053caf..3799ba0b 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -1,9 +1,10 @@ use borsh::BorshDeserialize; -use jito_restaking_core::avs_operator_ticket::AvsOperatorTicket; -use jito_restaking_core::avs_vault_slasher_ticket::AvsVaultSlasherTicket; -use jito_restaking_core::avs_vault_ticket::AvsVaultTicket; -use jito_restaking_core::operator_avs_ticket::OperatorAvsTicket; -use jito_restaking_core::operator_vault_ticket::OperatorVaultTicket; +use jito_restaking_core::{ + avs_operator_ticket::AvsOperatorTicket, avs_vault_slasher_ticket::AvsVaultSlasherTicket, + avs_vault_ticket::AvsVaultTicket, operator_avs_ticket::OperatorAvsTicket, + operator_vault_ticket::OperatorVaultTicket, +}; +use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawTicket; use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, @@ -30,194 +31,16 @@ use spl_token::{ instruction::initialize_mint2, state::{Account, Mint}, }; -// -// struct EnqueueWithdrawAccountData { -// config_pubkey: Pubkey, -// config: Config, -// -// vault_pubkey: Pubkey, -// vault: Vault, -// -// vault_delegation_list_pubkey: Pubkey, -// vault_delegation_list: VaultDelegationList, -// -// vault_staker_withdraw_ticket: Pubkey, -// -// vault_staker_withdraw_ticket_token_account: Pubkey, -// -// staker: Keypair, -// -// staker_lrt_token_account: Pubkey, -// -// base: Keypair, -// } - -// fn prepare_accounts( -// vault_index: u64, -// deposit_fee_bps: u16, -// withdrawal_fee_bps: u16, -// ) -> EnqueueWithdrawAccountData { -// let (config_pubkey, config_bump, _) = Config::find_program_address(&jito_vault_program::id()); -// let mut config = Config::new( -// Pubkey::new_unique(), -// jito_restaking_program::id(), -// config_bump, -// ); -// // assume the vault is created -// config.increment_vaults().unwrap(); -// -// let base = Pubkey::new_unique(); -// let (vault_pubkey, bump, _) = Vault::find_program_address(&jito_vault_program::id(), &base); -// let vault = Vault::new( -// Pubkey::new_unique(), -// Pubkey::new_unique(), -// Pubkey::new_unique(), -// vault_index, -// base, -// deposit_fee_bps, -// withdrawal_fee_bps, -// bump, -// ); -// -// let (vault_delegation_list_pubkey, vault_delegation_list_bump, _) = -// VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey); -// let vault_delegation_list = VaultDelegationList::new(vault_pubkey, vault_delegation_list_bump); -// -// let staker = Keypair::new(); -// -// let staker_lrt_token_account = -// get_associated_token_address(&staker.pubkey(), &vault.lrt_mint()); -// -// let base = Keypair::new(); -// -// let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::find_program_address( -// &jito_vault_program::id(), -// &vault_pubkey, -// &staker.pubkey(), -// &base.pubkey(), -// ) -// .0; -// let vault_staker_withdraw_ticket_token_account = -// get_associated_token_address(&vault_staker_withdraw_ticket, &vault.lrt_mint()); -// -// EnqueueWithdrawAccountData { -// config_pubkey, -// config, -// vault_pubkey, -// vault, -// vault_delegation_list_pubkey, -// vault_delegation_list, -// vault_staker_withdraw_ticket, -// vault_staker_withdraw_ticket_token_account, -// staker, -// staker_lrt_token_account, -// base, -// } -// } -// -// async fn write_enqueue_withdraw_accounts( -// accounts: &EnqueueWithdrawAccountData, -// fixture: &mut TestBuilder, -// ) { -// let EnqueueWithdrawAccountData { -// config_pubkey, -// config, -// vault_pubkey, -// vault, -// vault_delegation_list_pubkey, -// vault_delegation_list, -// vault_staker_withdraw_ticket: _, // created in the function -// vault_staker_withdraw_ticket_token_account, -// staker, -// staker_lrt_token_account, -// base: _, -// } = accounts; -// -// fixture -// .store_borsh_account(&config_pubkey, &jito_vault_program::id(), &config) -// .await -// .unwrap(); -// -// fixture -// .store_borsh_account(vault_pubkey, &jito_vault_program::id(), &vault) -// .await -// .unwrap(); -// let mut mint_buf = [0; Mint::LEN]; -// Mint { -// mint_authority: COption::Some(*vault_pubkey), -// supply: 0, -// decimals: 9, -// is_initialized: true, -// freeze_authority: COption::None, -// } -// .pack_into_slice(&mut mint_buf); -// fixture -// .store_account(&vault.lrt_mint(), &spl_token::id(), &mint_buf) -// .await -// .unwrap(); -// fixture -// .store_account(&vault.supported_mint(), &spl_token::id(), &mint_buf) -// .await -// .unwrap(); -// -// fixture -// .store_borsh_account( -// &VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0, -// &jito_vault_program::id(), -// &vault_delegation_list, -// ) -// .await -// .unwrap(); -// -// let mut token_account_buf = [0; Account::LEN]; -// // setup the LRT token account owned by the vault_staker_withdraw_ticket -// Account { -// mint: vault.lrt_mint(), -// owner: *vault_pubkey, -// amount: 0, -// delegate: COption::None, -// state: spl_token::state::AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut token_account_buf); -// fixture -// .store_account( -// vault_staker_withdraw_ticket_token_account, -// &spl_token::id(), -// &token_account_buf, -// ) -// .await -// .unwrap(); -// -// // setup the LRT token account owned by the staker -// Account { -// mint: vault.lrt_mint(), -// owner: staker.pubkey(), -// amount: 0, -// delegate: COption::None, -// state: spl_token::state::AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut token_account_buf); -// fixture -// .store_account( -// staker_lrt_token_account, -// &spl_token::id(), -// &token_account_buf, -// ) -// .await -// .unwrap(); -// } pub struct VaultRoot { pub vault_pubkey: Pubkey, pub vault_admin: Keypair, } +pub struct VaultStakerWithdrawTicketRoot { + pub base: Pubkey, +} + pub struct VaultProgramClient { banks_client: BanksClient, payer: Keypair, @@ -736,31 +559,6 @@ impl VaultProgramClient { .await } - // pub async fn remove_avs( - // &mut self, - // config: &Pubkey, - // vault: &Pubkey, - // avs: &Pubkey, - // vault_avs_ticket: &Pubkey, - // admin: &Keypair, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::remove_avs( - // &jito_vault_program::id(), - // config, - // vault, - // avs, - // vault_avs_ticket, - // &admin.pubkey(), - // )], - // Some(&admin.pubkey()), - // &[admin], - // blockhash, - // )) - // .await - // } - pub async fn add_operator( &mut self, config: &Pubkey, @@ -790,6 +588,52 @@ impl VaultProgramClient { .await } + pub async fn do_enqueue_withdraw( + &mut self, + vault_root: &VaultRoot, + depositor: &Keypair, + amount: u64, + ) -> Result { + let vault = self.get_vault(&vault_root.vault_pubkey).await.unwrap(); + let depositor_lrt_token_account = + get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); + + let base = Keypair::new(); + let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + &depositor.pubkey(), + &base.pubkey(), + ) + .0; + let vault_staker_withdraw_ticket_token_account = + get_associated_token_address(&vault_staker_withdraw_ticket, &vault.lrt_mint()); + + self.create_ata(&vault.lrt_mint(), &vault_staker_withdraw_ticket) + .await?; + + self.enqueue_withdraw( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0, + &vault_staker_withdraw_ticket, + &vault_staker_withdraw_ticket_token_account, + depositor, + &depositor_lrt_token_account, + &base, + amount, + ) + .await?; + + Ok(VaultStakerWithdrawTicketRoot { + base: base.pubkey(), + }) + } + pub async fn enqueue_withdraw( &mut self, config: &Pubkey, @@ -854,33 +698,6 @@ impl VaultProgramClient { .await } - // pub async fn remove_delegation( - // &mut self, - // config: &Pubkey, - // vault: &Pubkey, - // operator: &Pubkey, - // vault_delegation_list: &Pubkey, - // admin: &Keypair, - // amount: u64, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[remove_delegation( - // &jito_vault_program::id(), - // config, - // vault, - // operator, - // vault_delegation_list, - // &admin.pubkey(), - // amount, - // )], - // Some(&admin.pubkey()), - // &[admin], - // blockhash, - // )) - // .await - // } - pub async fn mint_to( &mut self, vault: &Pubkey, @@ -918,94 +735,6 @@ impl VaultProgramClient { .await } - // pub async fn set_deposit_capacity( - // &mut self, - // vault: &Pubkey, - // admin: &Keypair, - // amount: u64, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::set_deposit_capacity( - // &jito_vault_program::id(), - // vault, - // &admin.pubkey(), - // amount, - // )], - // Some(&admin.pubkey()), - // &[admin], - // blockhash, - // )) - // .await - // } - - // pub async fn set_admin( - // &mut self, - // vault: &Pubkey, - // old_admin: &Keypair, - // new_admin: &Pubkey, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::set_admin( - // &jito_vault_program::id(), - // vault, - // &old_admin.pubkey(), - // new_admin, - // )], - // Some(&old_admin.pubkey()), - // &[old_admin], - // blockhash, - // )) - // .await - // } - - // pub async fn set_secondary_admin( - // &mut self, - // vault: &Pubkey, - // admin: &Keypair, - // new_admin: &Pubkey, - // role: VaultAdminRole, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::set_secondary_admin( - // &jito_vault_program::id(), - // vault, - // &admin.pubkey(), - // new_admin, - // role, - // )], - // Some(&admin.pubkey()), - // &[admin], - // blockhash, - // )) - // .await - // } - - // pub async fn update_delegations( - // &mut self, - // config: &Pubkey, - // vault: &Pubkey, - // vault_delegation_list: &Pubkey, - // payer: &Keypair, - // ) -> Result<(), BanksClientError> { - // let blockhash = self.banks_client.get_latest_blockhash().await?; - // self.process_transaction(&Transaction::new_signed_with_payer( - // &[jito_vault_sdk::update_delegations( - // &jito_vault_program::id(), - // config, - // vault, - // vault_delegation_list, - // &payer.pubkey(), - // )], - // Some(&payer.pubkey()), - // &[payer], - // blockhash, - // )) - // .await - // } - pub async fn add_slasher( &mut self, config: &Pubkey, diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index d125be06..d74190dd 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,14 +1,15 @@ -use crate::fixtures::fixture::TestBuilder; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; +use crate::fixtures::fixture::TestBuilder; + #[tokio::test] async fn test_enqueue_withdrawal_success() { let mut fixture = TestBuilder::new().await; let mut vault_program_client = fixture.vault_program_client(); - let (vault_config_admin, vault_root) = + let (_vault_config_admin, vault_root) = vault_program_client.setup_vault(100, 100).await.unwrap(); let vault = vault_program_client @@ -50,4 +51,14 @@ async fn test_enqueue_withdrawal_success() { .await .unwrap(); assert_eq!(depositor_ata.amount, 99_000); + + vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, 49_500) + .await + .unwrap(); + + // let withdrawal = vault_program_client + // .enqueue_withdraw(&vault_root, &depositor, &depositor_lrt_token_account, 1000) + // .await + // .unwrap(); } diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index ab026d43..9ffc0451 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -1,8 +1,9 @@ -use crate::fixtures::fixture::TestBuilder; use jito_vault_core::vault_delegation_list::VaultDelegationList; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; +use crate::fixtures::fixture::TestBuilder; + #[tokio::test] async fn test_slash_ok() { let mut fixture = TestBuilder::new().await; diff --git a/vault_program/src/add_avs.rs b/vault_program/src/add_avs.rs index 93efc8a7..c657fd6b 100644 --- a/vault_program/src/add_avs.rs +++ b/vault_program/src/add_avs.rs @@ -129,16 +129,13 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; - msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; - msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; - msg!("c"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -146,18 +143,13 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; - msg!("d"); let vault_avs_ticket = EmptyAccount::sanitize(next_account_info(&mut accounts_iter)?, true)?; - msg!("e"); let admin = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; - msg!("f"); let payer = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, true)?; - msg!("g"); let system_program = SanitizedSystemProgram::sanitize(next_account_info(&mut accounts_iter)?)?; - msg!("h"); Ok(SanitizedAccounts { vault, diff --git a/vault_program/src/enqueue_withdraw.rs b/vault_program/src/enqueue_withdraw.rs index 71f60650..7e7d7d47 100644 --- a/vault_program/src/enqueue_withdraw.rs +++ b/vault_program/src/enqueue_withdraw.rs @@ -64,6 +64,8 @@ pub fn process_enqueue_withdraw( .checked_sub(fee_amount) .ok_or(ProgramError::ArithmeticOverflow)?; + msg!("a"); + // Find the redemption ratio at this point in time. // It may change in between this point in time and when the withdraw ticket is processed. // Stakers may get back less than redemption if there were accrued rewards accrued in between @@ -72,10 +74,14 @@ pub fn process_enqueue_withdraw( .vault() .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; + msg!("b"); + vault_delegation_list .vault_delegation_list_mut() .undelegate_for_withdraw(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; + msg!("c"); + _create_vault_staker_withdraw_ticket( program_id, &vault, @@ -89,6 +95,8 @@ pub fn process_enqueue_withdraw( amount_to_vault_staker_withdraw_ticket, )?; + msg!("d"); + // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account _transfer_to_vault_staker_withdraw_ticket( &token_program, From 7c9f572e2119e6a6ac89f65b2f1a9c032a01c988 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Wed, 31 Jul 2024 15:21:06 -0500 Subject: [PATCH 07/22] remove logs --- vault_program/src/enqueue_withdraw.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/vault_program/src/enqueue_withdraw.rs b/vault_program/src/enqueue_withdraw.rs index 7e7d7d47..9ade30c3 100644 --- a/vault_program/src/enqueue_withdraw.rs +++ b/vault_program/src/enqueue_withdraw.rs @@ -64,8 +64,6 @@ pub fn process_enqueue_withdraw( .checked_sub(fee_amount) .ok_or(ProgramError::ArithmeticOverflow)?; - msg!("a"); - // Find the redemption ratio at this point in time. // It may change in between this point in time and when the withdraw ticket is processed. // Stakers may get back less than redemption if there were accrued rewards accrued in between @@ -74,14 +72,10 @@ pub fn process_enqueue_withdraw( .vault() .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; - msg!("b"); - vault_delegation_list .vault_delegation_list_mut() .undelegate_for_withdraw(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; - msg!("c"); - _create_vault_staker_withdraw_ticket( program_id, &vault, @@ -95,8 +89,6 @@ pub fn process_enqueue_withdraw( amount_to_vault_staker_withdraw_ticket, )?; - msg!("d"); - // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account _transfer_to_vault_staker_withdraw_ticket( &token_program, @@ -218,39 +210,29 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; - msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; - msg!("b"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(accounts_iter)?, true, vault.account().key, )?; - msg!("c"); let vault_staker_withdraw_ticket = EmptyAccount::sanitize(next_account_info(accounts_iter)?, true)?; - msg!("d"); let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), vault_staker_withdraw_ticket.account().key, )?; - msg!("e"); let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; - msg!("f"); let staker_lrt_token_account = SanitizedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), staker.account().key, )?; - msg!("g"); let base = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, false)?; - msg!("h"); let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; - msg!("i"); let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; - msg!("j"); Ok(SanitizedAccounts { config, From 20d6bf84cdeef542723e626f7e3fa56e6e9f0e78 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Fri, 2 Aug 2024 13:56:25 -0500 Subject: [PATCH 08/22] werd --- .../tests/fixtures/vault_client.rs | 47 ++- .../tests/vault/enqueue_withdrawal.rs | 281 +++++++++++++++++- integration_tests/tests/vault/slash.rs | 17 +- restaking_core/src/config.rs | 6 +- vault_core/src/config.rs | 2 - vault_core/src/vault.rs | 2 +- vault_program/src/burn_withdraw_ticket.rs | 7 +- vault_program/src/enqueue_withdraw.rs | 29 +- vault_program/src/update_vault.rs | 2 +- vault_sdk/src/lib.rs | 8 +- 10 files changed, 352 insertions(+), 49 deletions(-) diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 3799ba0b..22abfe7a 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -37,6 +37,7 @@ pub struct VaultRoot { pub vault_admin: Keypair, } +#[derive(Debug)] pub struct VaultStakerWithdrawTicketRoot { pub base: Pubkey, } @@ -89,14 +90,35 @@ impl VaultProgramClient { pub async fn get_vault_delegation_list( &mut self, - account: &Pubkey, + vault_pubkey: &Pubkey, ) -> Result { - let account = self.banks_client.get_account(*account).await?.unwrap(); + let account = + VaultDelegationList::find_program_address(&jito_vault_program::id(), &vault_pubkey).0; + let account = self.banks_client.get_account(account).await?.unwrap(); Ok(VaultDelegationList::deserialize( &mut account.data.as_slice(), )?) } + pub async fn get_vault_staker_withdraw_ticket( + &mut self, + vault: &Pubkey, + staker: &Pubkey, + base: &Pubkey, + ) -> Result { + let account = VaultStakerWithdrawTicket::find_program_address( + &jito_vault_program::id(), + vault, + staker, + base, + ) + .0; + let account = self.banks_client.get_account(account).await?.unwrap(); + Ok(VaultStakerWithdrawTicket::deserialize( + &mut account.data.as_slice(), + )?) + } + pub async fn get_vault_avs_slasher_ticket( &mut self, vault: &Pubkey, @@ -634,6 +656,27 @@ impl VaultProgramClient { }) } + pub async fn do_update_vault(&mut self, vault_pubkey: &Pubkey) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + + let vault = self.get_vault(vault_pubkey).await?; + + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::update_vault( + &jito_vault_program::id(), + &Config::find_program_address(&jito_vault_program::id()).0, + vault_pubkey, + &VaultDelegationList::find_program_address(&jito_vault_program::id(), vault_pubkey) + .0, + &get_associated_token_address(vault_pubkey, &vault.supported_mint()), + )], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + )) + .await + } + pub async fn enqueue_withdraw( &mut self, config: &Pubkey, diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index d74190dd..50996b5c 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,10 +1,11 @@ +use crate::fixtures::fixture::TestBuilder; +use crate::fixtures::vault_client::VaultStakerWithdrawTicketRoot; +use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawTicket; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; -use crate::fixtures::fixture::TestBuilder; - #[tokio::test] -async fn test_enqueue_withdrawal_success() { +async fn test_enqueue_withdraw_more_than_staked_fails() { let mut fixture = TestBuilder::new().await; let mut vault_program_client = fixture.vault_program_client(); @@ -55,10 +56,276 @@ async fn test_enqueue_withdrawal_success() { vault_program_client .do_enqueue_withdraw(&vault_root, &depositor, 49_500) .await + .unwrap_err(); +} + +#[tokio::test] +async fn test_enqueue_withdraw_one_to_one_success() { + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_pool_client = fixture.restaking_program_client(); + + let (_vault_config_admin, vault_root) = + vault_program_client.setup_vault(100, 100).await.unwrap(); + + let _restaking_config_admin = restaking_pool_client.setup_config().await.unwrap(); + + let operator_root = restaking_pool_client.setup_operator().await.unwrap(); + let avs_root = restaking_pool_client.setup_avs().await.unwrap(); + + restaking_pool_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_pool_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + restaking_pool_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + restaking_pool_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await .unwrap(); - // let withdrawal = vault_program_client - // .enqueue_withdraw(&vault_root, &depositor, &depositor_lrt_token_account, 1000) - // .await - // .unwrap(); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + let vault_lrt_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_lrt_account.amount, 99_000); + + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 100_000); + assert_eq!(delegation.delegated_security().unwrap(), 100_000); + + let VaultStakerWithdrawTicketRoot { base } = vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, 99_000) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + // this is 1,000 because 1% of the fee went to the vault fee account, the assets still staked + // are for the LRT in the fee account to unstake later + assert_eq!(delegation.staked_amount(), 1_000); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 99_000); + assert_eq!(delegation.delegated_security().unwrap(), 100_000); + + let vault_staker_withdraw_ticket = vault_program_client + .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) + .await + .unwrap(); + assert_eq!(vault_staker_withdraw_ticket.lrt_amount(), 99_000); + assert_eq!( + vault_staker_withdraw_ticket.withdraw_allocation_amount(), + 99_000 + ); } + +#[tokio::test] +async fn test_enqueue_withdraw_with_reward_not_delegated_ok() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + // Setup vault with initial deposit + let (_vault_config_admin, vault_root) = vault_program_client.setup_vault(0, 0).await.unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + // Setup operator and AVS + let operator_root = restaking_program_client.setup_operator().await.unwrap(); + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + + // Setup necessary relationships + restaking_program_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + restaking_program_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // Initial deposit + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + // Mint LRT tokens to depositor + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + // Delegate all funds to the operator + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) + .await + .unwrap(); + + // Simulate rewards by adding more tokens to the vault + fixture + .mint_to(&vault.supported_mint(), &vault_root.vault_pubkey, 10_000) + .await + .unwrap(); + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // Enqueue withdrawal for half of the original deposit + let withdraw_amount = 50_000; + let VaultStakerWithdrawTicketRoot { base } = vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, withdraw_amount) + .await + .unwrap(); + + // Verify the withdraw ticket + let withdraw_ticket = vault_program_client + .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) + .await + .unwrap(); + + assert_eq!(withdraw_ticket.lrt_amount(), withdraw_amount); + + // The actual assets to be withdrawn should be more than the LRT amount due to rewards + assert_eq!(withdraw_ticket.withdraw_allocation_amount(), 55_000); + + // Verify the vault delegation list + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 45_000); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 55_000); + assert_eq!(delegation.delegated_security().unwrap(), 100_000); +} + +#[tokio::test] +async fn test_enqueue_withdraw_with_slash_ok() {} + +#[tokio::test] +async fn test_enqueue_withdraw_with_multiple_operators_pro_rata_ok() {} + +#[tokio::test] +async fn test_enqueue_withdraw_at_epoch_boundary() {} + +#[tokio::test] +async fn test_enqueue_withdraw_with_existing_cooldowns() {} + +#[tokio::test] +async fn test_enqueue_withdraw_with_zero_amount() {} + +#[tokio::test] +async fn test_enqueue_withdraw_insufficient_balance() {} + +#[tokio::test] +async fn test_enqueue_withdraw_concurrent_requests() {} + +#[tokio::test] +async fn test_enqueue_multiple_same_ticket() {} + +#[tokio::test] +async fn test_enqueue_delegation_list_update_needed() {} diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index 9ffc0451..43d28d23 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -1,4 +1,3 @@ -use jito_vault_core::vault_delegation_list::VaultDelegationList; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; @@ -108,13 +107,7 @@ async fn test_slash_ok() { .unwrap(); let vault_delegation_list = vault_program_client - .get_vault_delegation_list( - &VaultDelegationList::find_program_address( - &jito_vault_program::id(), - &vault_root.vault_pubkey, - ) - .0, - ) + .get_vault_delegation_list(&vault_root.vault_pubkey) .await .unwrap(); @@ -158,13 +151,7 @@ async fn test_slash_ok() { assert_eq!(vault.tokens_deposited(), 99_900); let delegation_list = vault_program_client - .get_vault_delegation_list( - &VaultDelegationList::find_program_address( - &jito_vault_program::id(), - &vault_root.vault_pubkey, - ) - .0, - ) + .get_vault_delegation_list(&vault_root.vault_pubkey) .await .unwrap(); let delegations = delegation_list.delegations(); diff --git a/restaking_core/src/config.rs b/restaking_core/src/config.rs index 0e19132b..24c30d78 100644 --- a/restaking_core/src/config.rs +++ b/restaking_core/src/config.rs @@ -1,10 +1,10 @@ -use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; - use crate::{ result::{RestakingCoreError, RestakingCoreResult}, AccountType, }; +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::clock::DEFAULT_SLOTS_PER_EPOCH; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] #[repr(C)] diff --git a/vault_core/src/config.rs b/vault_core/src/config.rs index d9d18c6d..9de65cbe 100644 --- a/vault_core/src/config.rs +++ b/vault_core/src/config.rs @@ -7,8 +7,6 @@ use crate::{ AccountType, }; -pub const MAX_RESTAKING_PROGRAMS: usize = 8; - #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] pub struct Config { /// The account type diff --git a/vault_core/src/vault.rs b/vault_core/src/vault.rs index b8ede6a4..15f1b88e 100644 --- a/vault_core/src/vault.rs +++ b/vault_core/src/vault.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs index e2cfd717..24e92738 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -20,6 +20,10 @@ use solana_program::{ }; use spl_token::instruction::{burn, close_account, transfer}; +/// Burns the withdraw ticket, transferring the assets to the staker and closing the withdraw ticket. +/// +/// One should call the [`crate::VaultInstruction::UpdateVault`] instruction before running this instruction +/// to ensure that any rewards that were accrued are accounted for. pub fn process_burn_withdraw_ticket( program_id: &Pubkey, accounts: &[AccountInfo], @@ -48,9 +52,6 @@ pub fn process_burn_withdraw_ticket( vault_staker_withdraw_ticket .vault_staker_withdraw_ticket() .check_withdrawable(slot, epoch_length)?; - vault_delegation_list - .vault_delegation_list_mut() - .check_update_needed(slot, epoch_length)?; // find the current redemption amount and the original redemption amount in the withdraw ticket let redemption_amount = vault.vault().calculate_assets_returned_amount( diff --git a/vault_program/src/enqueue_withdraw.rs b/vault_program/src/enqueue_withdraw.rs index 9ade30c3..c23e45e8 100644 --- a/vault_program/src/enqueue_withdraw.rs +++ b/vault_program/src/enqueue_withdraw.rs @@ -33,10 +33,13 @@ use spl_token::instruction::transfer; /// ratio increases due to rewards. However, if the vault has excess collateral that isn't staked, the vault /// can withdraw that excess and return it to the staker. If there's no excess, they can withdraw the /// amount that was set aside for withdraw. +/// +/// One should call the [`crate::VaultInstruction::UpdateVault`] instruction before running this instruction +/// to ensure that any rewards that were accrued are accounted for. pub fn process_enqueue_withdraw( program_id: &Pubkey, accounts: &[AccountInfo], - amount: u64, + lrt_amount: u64, ) -> ProgramResult { let SanitizedAccounts { config, @@ -59,18 +62,23 @@ pub fn process_enqueue_withdraw( .vault_delegation_list_mut() .check_update_needed(slot, epoch_length)?; - let fee_amount = vault.vault().calculate_withdraw_fee(amount)?; - let amount_to_vault_staker_withdraw_ticket = amount - .checked_sub(fee_amount) - .ok_or(ProgramError::ArithmeticOverflow)?; + // // TODO (LB): subtract fee! + // let fee_amount = vault.vault().calculate_withdraw_fee(lrt_amount)?; + // let amount_to_vault_staker_withdraw_ticket = lrt_amount + // .checked_sub(fee_amount) + // .ok_or(ProgramError::ArithmeticOverflow)?; // Find the redemption ratio at this point in time. // It may change in between this point in time and when the withdraw ticket is processed. // Stakers may get back less than redemption if there were accrued rewards accrued in between // this point and the redemption. - let amount_to_withdraw = vault - .vault() - .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; + let amount_to_withdraw = vault.vault().calculate_assets_returned_amount(lrt_amount)?; + msg!( + "lrt_supply: {} lrt_amount: {}, amount_to_withdraw: {}", + vault.vault().lrt_supply(), + lrt_amount, + amount_to_withdraw + ); vault_delegation_list .vault_delegation_list_mut() @@ -86,7 +94,7 @@ pub fn process_enqueue_withdraw( &rent, slot, amount_to_withdraw, - amount_to_vault_staker_withdraw_ticket, + lrt_amount, )?; // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account @@ -95,9 +103,8 @@ pub fn process_enqueue_withdraw( &staker_lrt_token_account, &vault_staker_withdraw_ticket_token_account, &staker, - amount_to_vault_staker_withdraw_ticket, + lrt_amount, )?; - // TODO (LB): transfer fee_amount of the LRT from the staker to the fee account vault_delegation_list.save()?; diff --git a/vault_program/src/update_vault.rs b/vault_program/src/update_vault.rs index 8f4635e6..d63ec663 100644 --- a/vault_program/src/update_vault.rs +++ b/vault_program/src/update_vault.rs @@ -21,10 +21,10 @@ pub fn process_update_vault(program_id: &Pubkey, accounts: &[AccountInfo]) -> Pr } = SanitizedAccounts::sanitize(program_id, accounts)?; let slot = Clock::get()?.slot; + vault_delegation_list .vault_delegation_list_mut() .update(slot, config.config().epoch_length())?; - vault .vault_mut() .set_tokens_deposited(vault_token_account.token_account().amount); diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index db64a8dd..2f67eb3d 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -566,18 +566,18 @@ pub fn remove_delegation( } } -pub fn update_delegations( +pub fn update_vault( program_id: &Pubkey, config: &Pubkey, vault: &Pubkey, vault_delegation_list: &Pubkey, - payer: &Pubkey, + vault_token_account: &Pubkey, ) -> Instruction { let accounts = vec![ AccountMeta::new_readonly(*config, false), - AccountMeta::new_readonly(*vault, false), + AccountMeta::new(*vault, false), AccountMeta::new(*vault_delegation_list, false), - AccountMeta::new(*payer, true), + AccountMeta::new(*vault_token_account, false), ]; Instruction { program_id: *program_id, From 0c390df9b265a34f3f97bf4296b3a0b35b2c04fd Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 15:53:34 -0500 Subject: [PATCH 09/22] fix some undelegation logic --- docs/index.md | 4 +- .../tests/fixtures/vault_client.rs | 16 +++- .../tests/vault/enqueue_withdrawal.rs | 31 +++++-- vault_core/src/lib.rs | 2 +- vault_core/src/operator_delegation.rs | 91 ++++++++++++++++--- vault_core/src/result.rs | 1 + vault_core/src/vault.rs | 2 +- vault_core/src/vault_delegation_list.rs | 2 - .../src/vault_staker_withdraw_ticket.rs | 14 +-- vault_program/src/burn_withdraw_ticket.rs | 8 +- ...ueue_withdraw.rs => enqueue_withdrawal.rs} | 70 ++++++++------ vault_program/src/lib.rs | 10 +- vault_program/src/mint_to.rs | 14 +-- vault_sdk/src/lib.rs | 17 ++-- 14 files changed, 191 insertions(+), 91 deletions(-) rename vault_program/src/{enqueue_withdraw.rs => enqueue_withdrawal.rs} (81%) diff --git a/docs/index.md b/docs/index.md index 28154018..dd239333 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,9 @@ title: Jito (Re)staking --- -Jito (Re)staking is a multi-asset staking and node consensus platform. +Jito (Re)staking is a multi-asset staking protocol for node consensus networks. The protocol tokenizes staked assets as +vault receipt tokens for enhanced liquidity and composability. Node consensus networks can use Jito Restaking to easily +customize staking parameters, slashing conditions, and economic incentives to tailor their security and tokenomics. ### Key Features diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 22abfe7a..4b416dff 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -4,7 +4,7 @@ use jito_restaking_core::{ avs_vault_ticket::AvsVaultTicket, operator_avs_ticket::OperatorAvsTicket, operator_vault_ticket::OperatorVaultTicket, }; -use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawTicket; +use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket; use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, @@ -105,8 +105,8 @@ impl VaultProgramClient { vault: &Pubkey, staker: &Pubkey, base: &Pubkey, - ) -> Result { - let account = VaultStakerWithdrawTicket::find_program_address( + ) -> Result { + let account = VaultStakerWithdrawalTicket::find_program_address( &jito_vault_program::id(), vault, staker, @@ -114,7 +114,7 @@ impl VaultProgramClient { ) .0; let account = self.banks_client.get_account(account).await?.unwrap(); - Ok(VaultStakerWithdrawTicket::deserialize( + Ok(VaultStakerWithdrawalTicket::deserialize( &mut account.data.as_slice(), )?) } @@ -621,7 +621,7 @@ impl VaultProgramClient { get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); let base = Keypair::new(); - let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::find_program_address( + let vault_staker_withdraw_ticket = VaultStakerWithdrawalTicket::find_program_address( &jito_vault_program::id(), &vault_root.vault_pubkey, &depositor.pubkey(), @@ -634,6 +634,9 @@ impl VaultProgramClient { self.create_ata(&vault.lrt_mint(), &vault_staker_withdraw_ticket) .await?; + let vault_staker_fee_token_account = + get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()); + self.enqueue_withdraw( &Config::find_program_address(&jito_vault_program::id()).0, &vault_root.vault_pubkey, @@ -644,6 +647,7 @@ impl VaultProgramClient { .0, &vault_staker_withdraw_ticket, &vault_staker_withdraw_ticket_token_account, + &vault_staker_fee_token_account, depositor, &depositor_lrt_token_account, &base, @@ -684,6 +688,7 @@ impl VaultProgramClient { vault_delegation_list: &Pubkey, vault_staker_withdraw_ticket: &Pubkey, vault_staker_withdraw_ticket_token_account: &Pubkey, + vault_fee_token_account: &Pubkey, staker: &Keypair, staker_lrt_token_account: &Pubkey, base: &Keypair, @@ -698,6 +703,7 @@ impl VaultProgramClient { vault_delegation_list, vault_staker_withdraw_ticket, vault_staker_withdraw_ticket_token_account, + vault_fee_token_account, &staker.pubkey(), staker_lrt_token_account, &base.pubkey(), diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index 50996b5c..b31fcd2f 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,6 +1,6 @@ use crate::fixtures::fixture::TestBuilder; use crate::fixtures::vault_client::VaultStakerWithdrawTicketRoot; -use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawTicket; +use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; @@ -60,7 +60,7 @@ async fn test_enqueue_withdraw_more_than_staked_fails() { } #[tokio::test] -async fn test_enqueue_withdraw_one_to_one_success() { +async fn test_enqueue_withdraw_with_fee_success() { let mut fixture = TestBuilder::new().await; let mut vault_program_client = fixture.vault_program_client(); @@ -142,6 +142,15 @@ async fn test_enqueue_withdraw_one_to_one_success() { .unwrap(); assert_eq!(vault_lrt_account.amount, 99_000); + let vault_fee_account = fixture + .get_token_account(&get_associated_token_address( + &vault.fee_owner(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_fee_account.amount, 1_000); + vault_program_client .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) .await @@ -156,6 +165,8 @@ async fn test_enqueue_withdraw_one_to_one_success() { assert_eq!(delegation.staked_amount(), 100_000); assert_eq!(delegation.delegated_security().unwrap(), 100_000); + // the user is withdrawing 99,000 LRT tokens, there is a 1% fee on withdraws, so + // 98010 tokens will be undeleged for withdraw let VaultStakerWithdrawTicketRoot { base } = vault_program_client .do_enqueue_withdraw(&vault_root, &depositor, 99_000) .await @@ -169,23 +180,23 @@ async fn test_enqueue_withdraw_one_to_one_success() { let delegation = vault_delegation_list.delegations().get(0).unwrap(); // this is 1,000 because 1% of the fee went to the vault fee account, the assets still staked // are for the LRT in the fee account to unstake later - assert_eq!(delegation.staked_amount(), 1_000); - assert_eq!(delegation.enqueued_for_withdraw_amount(), 99_000); + assert_eq!(delegation.staked_amount(), 1_990); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 98_010); assert_eq!(delegation.delegated_security().unwrap(), 100_000); let vault_staker_withdraw_ticket = vault_program_client .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) .await .unwrap(); - assert_eq!(vault_staker_withdraw_ticket.lrt_amount(), 99_000); + assert_eq!(vault_staker_withdraw_ticket.lrt_amount(), 98_010); assert_eq!( vault_staker_withdraw_ticket.withdraw_allocation_amount(), - 99_000 + 98_010 ); } #[tokio::test] -async fn test_enqueue_withdraw_with_reward_not_delegated_ok() { +async fn test_enqueue_withdraw_with_reward_ok() { let mut fixture = TestBuilder::new().await; let mut vault_program_client = fixture.vault_program_client(); let mut restaking_program_client = fixture.restaking_program_client(); @@ -329,3 +340,9 @@ async fn test_enqueue_multiple_same_ticket() {} #[tokio::test] async fn test_enqueue_delegation_list_update_needed() {} + +#[tokio::test] +async fn test_enqueue_withdraw_with_all_assets_cooling_down() {} + +#[tokio::test] +async fn test_enqueue_withdraw_with_partially_cooling_down_assets() {} diff --git a/vault_core/src/lib.rs b/vault_core/src/lib.rs index 916b423f..da9627b9 100644 --- a/vault_core/src/lib.rs +++ b/vault_core/src/lib.rs @@ -21,6 +21,6 @@ enum AccountType { VaultAvsTicket, VaultDelegationList, VaultAvsSlasherOperatorTicket, - VaultStakerWithdrawTicket, + VaultStakerWithdrawalTicket, VaultStakerWithdrawTicketEmpty, } diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 7fbe97e5..f5daeeb6 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -1,5 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; +use std::cmp::min; use crate::result::{VaultCoreError, VaultCoreResult}; @@ -82,12 +83,18 @@ impl OperatorDelegation { pub fn delegated_security(&self) -> VaultCoreResult { self.staked_amount .checked_add(self.enqueued_for_cooldown_amount) - .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? - .checked_add(self.cooling_down_amount) - .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? - .checked_add(self.enqueued_for_withdraw_amount) - .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow)? - .checked_add(self.cooling_down_for_withdraw_amount) + .and_then(|x| x.checked_add(self.cooling_down_amount)) + .and_then(|x| x.checked_add(self.enqueued_for_withdraw_amount)) + .and_then(|x| x.checked_add(self.cooling_down_for_withdraw_amount)) + .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow) + } + + /// Returns the amount of withdrawable security, which is the sum of the amount actively staked, + /// the amount enqueued for cooldown, and the cooling down amount. + pub fn amount_available_to_withdraw(&self) -> VaultCoreResult { + self.staked_amount + .checked_add(self.enqueued_for_cooldown_amount) + .and_then(|x| x.checked_add(self.cooling_down_amount)) .ok_or(VaultCoreError::VaultOperatorActiveStakeOverflow) } @@ -120,10 +127,7 @@ impl OperatorDelegation { .ok_or(VaultCoreError::VaultSlashingOverflow)? .checked_div(total_security_amount as u128) .ok_or(VaultCoreError::VaultSlashingDivisionByZero)?; - let actual_slash = std::cmp::min( - pro_rata_slash as u64, - std::cmp::min(*amount, remaining_slash), - ); + let actual_slash = min(pro_rata_slash as u64, min(*amount, remaining_slash)); *amount = amount .checked_sub(actual_slash) .ok_or(VaultCoreError::VaultSlashingUnderflow)?; @@ -161,11 +165,41 @@ impl OperatorDelegation { Ok(()) } + /// Un-delegates assets for withdraw from the operator. If the total amount to withdraw is greater + /// than the staked amount, it pulls from the enqueued_for_cooldown_amount. If there is still excess, + /// it pulls from the cooling_down_amount. pub fn undelegate_for_withdraw(&mut self, amount: u64) -> VaultCoreResult<()> { + if amount > self.amount_available_to_withdraw()? { + return Err(VaultCoreError::VaultDelegationListInsufficientSecurity); + } + + let mut amount_left = amount; + + let staked_amount_withdraw = min(self.staked_amount, amount_left); self.staked_amount = self .staked_amount - .checked_sub(amount) + .checked_sub(staked_amount_withdraw) + .ok_or(VaultCoreError::VaultUndelegationUnderflow)?; + amount_left = amount_left + .checked_sub(staked_amount_withdraw) + .ok_or(VaultCoreError::VaultUndelegationUnderflow)?; + + let enqueued_for_cooldown_amount_withdraw = + min(self.enqueued_for_cooldown_amount, amount_left); + self.enqueued_for_cooldown_amount = self + .enqueued_for_cooldown_amount + .checked_sub(enqueued_for_cooldown_amount_withdraw) + .ok_or(VaultCoreError::VaultUndelegationUnderflow)?; + amount_left = amount_left + .checked_sub(enqueued_for_cooldown_amount_withdraw) + .ok_or(VaultCoreError::VaultUndelegationUnderflow)?; + + let cooldown_amount_withdraw = min(self.cooling_down_amount, amount_left); + self.cooling_down_amount = self + .cooling_down_amount + .checked_sub(cooldown_amount_withdraw) .ok_or(VaultCoreError::VaultDelegationUnderflow)?; + self.enqueued_for_withdraw_amount = self .enqueued_for_withdraw_amount .checked_add(amount) @@ -257,6 +291,39 @@ mod tests { operator_delegation.slash(5_000).unwrap(); assert_eq!(operator_delegation.delegated_security().unwrap(), 95_000); - // assert_eq!(operator_delegation.staked_amount(), 85_000); + assert_eq!(operator_delegation.staked_amount(), 85_500); + } + + #[test] + fn test_undelegate_for_withdraw_with_cooling_down() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100_000).unwrap(); + assert_eq!(operator_delegation.staked_amount(), 100_000); + + operator_delegation.undelegate(10_000).unwrap(); + assert_eq!(operator_delegation.staked_amount(), 90_000); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 10_000); + + operator_delegation.undelegate_for_withdraw(95_000).unwrap(); + assert_eq!(operator_delegation.staked_amount(), 0); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 5_000); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 95_000); + } + + #[test] + fn test_undelegate_for_withdraw_not_enough_security() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100_000).unwrap(); + + operator_delegation + .undelegate_for_withdraw(100_001) + .unwrap_err(); + + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + operator_delegation.delegate(100_000).unwrap(); + operator_delegation.undelegate_for_withdraw(50_000).unwrap(); + operator_delegation + .undelegate_for_withdraw(50_001) + .unwrap_err(); } } diff --git a/vault_core/src/result.rs b/vault_core/src/result.rs index 13ba5db2..c7163a82 100644 --- a/vault_core/src/result.rs +++ b/vault_core/src/result.rs @@ -115,6 +115,7 @@ pub enum VaultCoreError { VaultDelegationListUpdateRequired, VaultStakerWithdrawTicketOverflow, VaultStakerWithdrawTicketNotWithdrawable, + VaultUndelegationUnderflow, } impl From for ProgramError { diff --git a/vault_core/src/vault.rs b/vault_core/src/vault.rs index 15f1b88e..b8ede6a4 100644 --- a/vault_core/src/vault.rs +++ b/vault_core/src/vault.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index b14f7d56..85188700 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -207,8 +207,6 @@ impl VaultDelegationList { fn undelegate_for_withdraw_pro_rata(&mut self, amount: u64) -> VaultCoreResult<()> { let total_delegated = self.delegated_security()?; - // TODO (LB): what if want to withdraw > than the total delegated amount? - // one could set aside undelegated assets into the withdraw bucket? if amount > total_delegated || total_delegated == 0 { return Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds); } diff --git a/vault_core/src/vault_staker_withdraw_ticket.rs b/vault_core/src/vault_staker_withdraw_ticket.rs index ac13c2e3..9b73412e 100644 --- a/vault_core/src/vault_staker_withdraw_ticket.rs +++ b/vault_core/src/vault_staker_withdraw_ticket.rs @@ -11,7 +11,7 @@ use crate::{ /// with the staker's LRT. #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] #[repr(C)] -pub struct VaultStakerWithdrawTicket { +pub struct VaultStakerWithdrawalTicket { /// The account type account_type: AccountType, @@ -39,7 +39,7 @@ pub struct VaultStakerWithdrawTicket { bump: u8, } -impl VaultStakerWithdrawTicket { +impl VaultStakerWithdrawalTicket { pub const fn new( vault: Pubkey, staker: Pubkey, @@ -50,7 +50,7 @@ impl VaultStakerWithdrawTicket { bump: u8, ) -> Self { Self { - account_type: AccountType::VaultStakerWithdrawTicket, + account_type: AccountType::VaultStakerWithdrawalTicket, vault, staker, base, @@ -159,7 +159,7 @@ impl VaultStakerWithdrawTicket { pub struct SanitizedVaultStakerWithdrawTicket<'a, 'info> { account: &'a AccountInfo<'info>, - vault_staker_withdraw_ticket: VaultStakerWithdrawTicket, + vault_staker_withdraw_ticket: VaultStakerWithdrawalTicket, } impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { @@ -175,7 +175,7 @@ impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { } let vault_staker_withdraw_ticket = - VaultStakerWithdrawTicket::deserialize_checked(program_id, account, vault, staker)?; + VaultStakerWithdrawalTicket::deserialize_checked(program_id, account, vault, staker)?; Ok(SanitizedVaultStakerWithdrawTicket { account, @@ -183,11 +183,11 @@ impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { }) } - pub const fn vault_staker_withdraw_ticket(&self) -> &VaultStakerWithdrawTicket { + pub const fn vault_staker_withdraw_ticket(&self) -> &VaultStakerWithdrawalTicket { &self.vault_staker_withdraw_ticket } - pub fn vault_staker_withdraw_ticket_mut(&mut self) -> &mut VaultStakerWithdrawTicket { + pub fn vault_staker_withdraw_ticket_mut(&mut self) -> &mut VaultStakerWithdrawalTicket { &mut self.vault_staker_withdraw_ticket } diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs index 24e92738..06c46647 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -7,7 +7,9 @@ use jito_vault_core::{ config::SanitizedConfig, vault::{SanitizedVault, Vault}, vault_delegation_list::SanitizedVaultDelegationList, - vault_staker_withdraw_ticket::{SanitizedVaultStakerWithdrawTicket, VaultStakerWithdrawTicket}, + vault_staker_withdraw_ticket::{ + SanitizedVaultStakerWithdrawTicket, VaultStakerWithdrawalTicket, + }, }; use solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -171,7 +173,7 @@ fn _close_token_account<'a, 'info>( staker_lrt_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, ) -> ProgramResult { // TODO: combine with burn lrt method - let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + let (_, bump, mut seeds) = VaultStakerWithdrawalTicket::find_program_address( program_id, vault.account().key, staker.account().key, @@ -258,7 +260,7 @@ fn _burn_lrt<'a, 'info>( token_mint: &SanitizedTokenMint<'a, 'info>, burn_amount: u64, ) -> ProgramResult { - let (_, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + let (_, bump, mut seeds) = VaultStakerWithdrawalTicket::find_program_address( program_id, vault.account().key, staker.account().key, diff --git a/vault_program/src/enqueue_withdraw.rs b/vault_program/src/enqueue_withdrawal.rs similarity index 81% rename from vault_program/src/enqueue_withdraw.rs rename to vault_program/src/enqueue_withdrawal.rs index c23e45e8..17b7ef70 100644 --- a/vault_program/src/enqueue_withdraw.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -9,7 +9,7 @@ use jito_vault_core::{ config::SanitizedConfig, vault::SanitizedVault, vault_delegation_list::{SanitizedVaultDelegationList, UndelegateForWithdrawMethod}, - vault_staker_withdraw_ticket::VaultStakerWithdrawTicket, + vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket, }; use solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -36,7 +36,7 @@ use spl_token::instruction::transfer; /// /// One should call the [`crate::VaultInstruction::UpdateVault`] instruction before running this instruction /// to ensure that any rewards that were accrued are accounted for. -pub fn process_enqueue_withdraw( +pub fn process_enqueue_withdrawal( program_id: &Pubkey, accounts: &[AccountInfo], lrt_amount: u64, @@ -47,10 +47,10 @@ pub fn process_enqueue_withdraw( mut vault_delegation_list, vault_staker_withdraw_ticket, vault_staker_withdraw_ticket_token_account, + vault_fee_token_account, staker, staker_lrt_token_account, base, - token_program, system_program, } = SanitizedAccounts::sanitize(program_id, accounts)?; @@ -62,21 +62,24 @@ pub fn process_enqueue_withdraw( .vault_delegation_list_mut() .check_update_needed(slot, epoch_length)?; - // // TODO (LB): subtract fee! - // let fee_amount = vault.vault().calculate_withdraw_fee(lrt_amount)?; - // let amount_to_vault_staker_withdraw_ticket = lrt_amount - // .checked_sub(fee_amount) - // .ok_or(ProgramError::ArithmeticOverflow)?; + // The withdraw fee is subtracted here as opposed to when the withdraw ticket is processed + // so the amount representing the fee isn't unstaked. + let fee_amount = vault.vault().calculate_withdraw_fee(lrt_amount)?; + let amount_to_vault_staker_withdraw_ticket = lrt_amount + .checked_sub(fee_amount) + .ok_or(ProgramError::ArithmeticOverflow)?; // Find the redemption ratio at this point in time. // It may change in between this point in time and when the withdraw ticket is processed. // Stakers may get back less than redemption if there were accrued rewards accrued in between // this point and the redemption. - let amount_to_withdraw = vault.vault().calculate_assets_returned_amount(lrt_amount)?; + let amount_to_withdraw = vault + .vault() + .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; msg!( "lrt_supply: {} lrt_amount: {}, amount_to_withdraw: {}", vault.vault().lrt_supply(), - lrt_amount, + amount_to_vault_staker_withdraw_ticket, amount_to_withdraw ); @@ -94,16 +97,21 @@ pub fn process_enqueue_withdraw( &rent, slot, amount_to_withdraw, - lrt_amount, + amount_to_vault_staker_withdraw_ticket, )?; // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account - _transfer_to_vault_staker_withdraw_ticket( - &token_program, + _transfer_to( &staker_lrt_token_account, &vault_staker_withdraw_ticket_token_account, &staker, - lrt_amount, + amount_to_vault_staker_withdraw_ticket, + )?; + _transfer_to( + &staker_lrt_token_account, + &vault_fee_token_account, + &staker, + fee_amount, )?; vault_delegation_list.save()?; @@ -111,25 +119,24 @@ pub fn process_enqueue_withdraw( Ok(()) } -fn _transfer_to_vault_staker_withdraw_ticket<'a, 'info>( - token_program: &SanitizedTokenProgram, - staker_lrt_token_account: &SanitizedTokenAccount<'a, 'info>, - vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, +fn _transfer_to<'a, 'info>( + from: &SanitizedTokenAccount<'a, 'info>, + to: &SanitizedAssociatedTokenAccount<'a, 'info>, staker: &SanitizedSignerAccount<'a, 'info>, amount: u64, ) -> ProgramResult { invoke( &transfer( - token_program.account().key, - staker_lrt_token_account.account().key, - vault_staker_withdraw_ticket_token_account.account().key, + &spl_token::id(), + from.account().key, + to.account().key, staker.account().key, &[], amount, )?, &[ - staker_lrt_token_account.account().clone(), - vault_staker_withdraw_ticket_token_account.account().clone(), + from.account().clone(), + to.account().clone(), staker.account().clone(), ], ) @@ -148,7 +155,7 @@ fn _create_vault_staker_withdraw_ticket<'a, 'info>( amount_to_withdraw: u64, amount_to_vault_staker_withdraw_ticket: u64, ) -> ProgramResult { - let (address, bump, mut seeds) = VaultStakerWithdrawTicket::find_program_address( + let (address, bump, mut seeds) = VaultStakerWithdrawalTicket::find_program_address( program_id, vault.account().key, staker.account().key, @@ -162,7 +169,7 @@ fn _create_vault_staker_withdraw_ticket<'a, 'info>( "Vault staker withdraw ticket is not at the correct PDA", )?; - let vault_staker_withdraw_ticket = VaultStakerWithdrawTicket::new( + let vault_staker_withdraw_ticket = VaultStakerWithdrawalTicket::new( *vault.account().key, *staker.account().key, *base.account().key, @@ -200,15 +207,15 @@ struct SanitizedAccounts<'a, 'info> { vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, vault_staker_withdraw_ticket: EmptyAccount<'a, 'info>, vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + vault_fee_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, staker: SanitizedSignerAccount<'a, 'info>, staker_lrt_token_account: SanitizedTokenAccount<'a, 'info>, base: SanitizedSignerAccount<'a, 'info>, - token_program: SanitizedTokenProgram<'a, 'info>, system_program: SanitizedSystemProgram<'a, 'info>, } impl<'a, 'info> SanitizedAccounts<'a, 'info> { - /// Loads accounts for [`crate::VaultInstruction::EnqueueWithdraw`] + /// Loads accounts for [`crate::VaultInstruction::EnqueueWithdrawal`] fn sanitize( program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -231,6 +238,11 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { &vault.vault().lrt_mint(), vault_staker_withdraw_ticket.account().key, )?; + let vault_fee_token_account = SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().lrt_mint(), + &vault.vault().fee_owner(), + )?; let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; let staker_lrt_token_account = SanitizedTokenAccount::sanitize( next_account_info(accounts_iter)?, @@ -238,7 +250,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { staker.account().key, )?; let base = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, false)?; - let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; let system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; Ok(SanitizedAccounts { @@ -247,10 +259,10 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault_delegation_list, vault_staker_withdraw_ticket, vault_staker_withdraw_ticket_token_account, + vault_fee_token_account, staker, staker_lrt_token_account, base, - token_program, system_program, }) } diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index 3605faa2..58644ccb 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -5,7 +5,7 @@ mod add_slasher; mod burn; mod burn_withdraw_ticket; mod create_token_metadata; -mod enqueue_withdraw; +mod enqueue_withdrawal; mod initialize_config; mod initialize_vault; mod initialize_vault_avs_slasher_operator_ticket; @@ -36,7 +36,7 @@ use crate::{ add_operator::process_vault_add_operator, add_slasher::process_add_slasher, burn::process_burn, burn_withdraw_ticket::process_burn_withdraw_ticket, create_token_metadata::process_create_token_metadata, - enqueue_withdraw::process_enqueue_withdraw, initialize_config::process_initialize_config, + enqueue_withdrawal::process_enqueue_withdrawal, initialize_config::process_initialize_config, initialize_vault::process_initialize_vault, initialize_vault_avs_slasher_operator_ticket::process_initialize_vault_avs_slasher_operator_ticket, initialize_vault_with_mint::process_initialize_vault_with_mint, mint_to::process_mint, @@ -124,9 +124,9 @@ pub fn process_instruction( msg!("Instruction: Burn"); process_burn(program_id, accounts, amount) } - VaultInstruction::EnqueueWithdraw { amount } => { - msg!("Instruction: EnqueueWithdraw"); - process_enqueue_withdraw(program_id, accounts, amount) + VaultInstruction::EnqueueWithdrawal { amount } => { + msg!("Instruction: EnqueueWithdrawal"); + process_enqueue_withdrawal(program_id, accounts, amount) } VaultInstruction::BurnWithdrawTicket => { msg!("Instruction: BurnWithdrawTicket"); diff --git a/vault_program/src/mint_to.rs b/vault_program/src/mint_to.rs index d5cf7b9a..caef7d86 100644 --- a/vault_program/src/mint_to.rs +++ b/vault_program/src/mint_to.rs @@ -23,7 +23,6 @@ pub fn process_mint(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) vault_token_account, depositor_lrt_token_account, vault_fee_token_account, - token_program, mint_signer, } = SanitizedAccounts::sanitize(program_id, accounts)?; @@ -48,7 +47,6 @@ pub fn process_mint(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) .set_tokens_deposited(vault_token_account.token_account().amount); _transfer_to_vault( - &token_program, &depositor_token_account, &vault_token_account, &depositor, @@ -64,7 +62,6 @@ pub fn process_mint(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) // mint LRT to user and fee wallet _mint_lrt( program_id, - &token_program, &vault, &lrt_mint, &depositor_lrt_token_account, @@ -72,7 +69,6 @@ pub fn process_mint(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) )?; _mint_lrt( program_id, - &token_program, &vault, &lrt_mint, &vault_fee_token_account, @@ -92,7 +88,6 @@ struct SanitizedAccounts<'a, 'info> { vault_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, depositor_lrt_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, vault_fee_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, - token_program: SanitizedTokenProgram<'a, 'info>, mint_signer: Option>, } @@ -127,7 +122,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { &vault.vault().lrt_mint(), &vault.vault().fee_owner(), )?; - let token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; + let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; let mint_signer = if vault.vault().mint_burn_authority().is_some() { Some(SanitizedSignerAccount::sanitize( next_account_info(accounts_iter)?, @@ -145,7 +140,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault_token_account, depositor_lrt_token_account, vault_fee_token_account, - token_program, mint_signer, }) } @@ -160,7 +154,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { /// * `owner` - The owner of the source token account /// * `amount` - The amount of tokens to transfer fn _transfer_to_vault<'a, 'info>( - token_program: &SanitizedTokenProgram, depositor_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, vault_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, owner: &SanitizedSignerAccount<'a, 'info>, @@ -168,7 +161,7 @@ fn _transfer_to_vault<'a, 'info>( ) -> ProgramResult { invoke( &transfer( - token_program.account().key, + &spl_token::id(), depositor_token_account.account().key, vault_token_account.account().key, owner.account().key, @@ -185,7 +178,6 @@ fn _transfer_to_vault<'a, 'info>( fn _mint_lrt<'a, 'info>( program_id: &Pubkey, - token_program: &SanitizedTokenProgram, vault: &SanitizedVault<'a, 'info>, lrt_mint: &SanitizedTokenMint<'a, 'info>, depositor_lrt_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, @@ -197,7 +189,7 @@ fn _mint_lrt<'a, 'info>( invoke_signed( &mint_to( - token_program.account().key, + &spl_token::id(), lrt_mint.account().key, depositor_lrt_token_account.account().key, vault.account().key, diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index 2f67eb3d..c1e22860 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -98,12 +98,13 @@ pub enum VaultInstruction { #[account(2, writable, name = "vault_delegation_list")] #[account(3, writable, name = "vault_staker_withdraw_ticket")] #[account(4, writable, name = "vault_staker_withdraw_ticket_token_account")] - #[account(5, writable, signer, name = "staker")] - #[account(6, writable, name = "staker_lrt_token_account")] - #[account(7, signer, name = "base")] - #[account(8, name = "token_program")] - #[account(9, name = "system_program")] - EnqueueWithdraw { + #[account(5, writable, name = "vault_fee_token_account")] + #[account(6, writable, signer, name = "staker")] + #[account(7, writable, name = "staker_lrt_token_account")] + #[account(8, signer, name = "base")] + #[account(9, name = "token_program")] + #[account(10, name = "system_program")] + EnqueueWithdrawal { amount: u64 }, @@ -735,6 +736,7 @@ pub fn enqueue_withdraw( vault_delegation_list: &Pubkey, vault_staker_withdraw_ticket: &Pubkey, vault_staker_withdraw_ticket_token_account: &Pubkey, + vault_fee_token_account: &Pubkey, staker: &Pubkey, staker_lrt_token_account: &Pubkey, base: &Pubkey, @@ -746,6 +748,7 @@ pub fn enqueue_withdraw( AccountMeta::new(*vault_delegation_list, false), AccountMeta::new(*vault_staker_withdraw_ticket, false), AccountMeta::new(*vault_staker_withdraw_ticket_token_account, false), + AccountMeta::new(*vault_fee_token_account, false), AccountMeta::new(*staker, true), AccountMeta::new(*staker_lrt_token_account, false), AccountMeta::new_readonly(*base, true), @@ -755,7 +758,7 @@ pub fn enqueue_withdraw( Instruction { program_id: *program_id, accounts, - data: VaultInstruction::EnqueueWithdraw { amount } + data: VaultInstruction::EnqueueWithdrawal { amount } .try_to_vec() .unwrap(), } From 7f39967a88632c28ffa78cf1876834389b91827d Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 17:59:33 -0500 Subject: [PATCH 10/22] some undelegation fixes --- .../tests/fixtures/restaking_client.rs | 1 - .../tests/fixtures/vault_client.rs | 2 +- .../tests/vault/enqueue_withdrawal.rs | 10 +-- restaking_core/src/config.rs | 6 +- vault_core/src/operator_delegation.rs | 38 ++++---- vault_core/src/vault_delegation_list.rs | 90 +++++++++++-------- vault_program/src/burn_withdraw_ticket.rs | 6 +- 7 files changed, 85 insertions(+), 68 deletions(-) diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs index 11735461..1b25aa09 100644 --- a/integration_tests/tests/fixtures/restaking_client.rs +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -9,7 +9,6 @@ use jito_restaking_sdk::{ avs_add_operator, avs_add_vault, avs_add_vault_slasher, initialize_avs, initialize_config, initialize_operator, operator_add_avs, operator_add_vault, }; - use solana_program::{native_token::sol_to_lamports, pubkey::Pubkey, system_instruction::transfer}; use solana_program_test::{BanksClient, BanksClientError}; use solana_sdk::{ diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 4b416dff..25aa0235 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -4,11 +4,11 @@ use jito_restaking_core::{ avs_vault_ticket::AvsVaultTicket, operator_avs_ticket::OperatorAvsTicket, operator_vault_ticket::OperatorVaultTicket, }; -use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket; use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, vault_delegation_list::VaultDelegationList, vault_operator_ticket::VaultOperatorTicket, + vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket, }; use jito_vault_sdk::{add_delegation, initialize_config, initialize_vault}; use solana_program::{ diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index b31fcd2f..7c51ec02 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,9 +1,9 @@ -use crate::fixtures::fixture::TestBuilder; -use crate::fixtures::vault_client::VaultStakerWithdrawTicketRoot; use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; +use crate::fixtures::{fixture::TestBuilder, vault_client::VaultStakerWithdrawTicketRoot}; + #[tokio::test] async fn test_enqueue_withdraw_more_than_staked_fails() { let mut fixture = TestBuilder::new().await; @@ -163,7 +163,7 @@ async fn test_enqueue_withdraw_with_fee_success() { let delegation = vault_delegation_list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 100_000); - assert_eq!(delegation.delegated_security().unwrap(), 100_000); + assert_eq!(delegation.total_security().unwrap(), 100_000); // the user is withdrawing 99,000 LRT tokens, there is a 1% fee on withdraws, so // 98010 tokens will be undeleged for withdraw @@ -182,7 +182,7 @@ async fn test_enqueue_withdraw_with_fee_success() { // are for the LRT in the fee account to unstake later assert_eq!(delegation.staked_amount(), 1_990); assert_eq!(delegation.enqueued_for_withdraw_amount(), 98_010); - assert_eq!(delegation.delegated_security().unwrap(), 100_000); + assert_eq!(delegation.total_security().unwrap(), 100_000); let vault_staker_withdraw_ticket = vault_program_client .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) @@ -311,7 +311,7 @@ async fn test_enqueue_withdraw_with_reward_ok() { let delegation = vault_delegation_list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 45_000); assert_eq!(delegation.enqueued_for_withdraw_amount(), 55_000); - assert_eq!(delegation.delegated_security().unwrap(), 100_000); + assert_eq!(delegation.total_security().unwrap(), 100_000); } #[tokio::test] diff --git a/restaking_core/src/config.rs b/restaking_core/src/config.rs index 24c30d78..0e19132b 100644 --- a/restaking_core/src/config.rs +++ b/restaking_core/src/config.rs @@ -1,10 +1,10 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; + use crate::{ result::{RestakingCoreError, RestakingCoreResult}, AccountType, }; -use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::clock::DEFAULT_SLOTS_PER_EPOCH; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; #[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] #[repr(C)] diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index f5daeeb6..4f9230e8 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -1,6 +1,7 @@ +use std::cmp::min; + use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; -use std::cmp::min; use crate::result::{VaultCoreError, VaultCoreResult}; @@ -80,7 +81,7 @@ impl OperatorDelegation { /// # Returns /// The total amount of stake on the operator that can be applied for security, which includes /// the active and any cooling down stake for re-delegation or withdrawal - pub fn delegated_security(&self) -> VaultCoreResult { + pub fn total_security(&self) -> VaultCoreResult { self.staked_amount .checked_add(self.enqueued_for_cooldown_amount) .and_then(|x| x.checked_add(self.cooling_down_amount)) @@ -91,7 +92,7 @@ impl OperatorDelegation { /// Returns the amount of withdrawable security, which is the sum of the amount actively staked, /// the amount enqueued for cooldown, and the cooling down amount. - pub fn amount_available_to_withdraw(&self) -> VaultCoreResult { + pub fn withdrawable_security(&self) -> VaultCoreResult { self.staked_amount .checked_add(self.enqueued_for_cooldown_amount) .and_then(|x| x.checked_add(self.cooling_down_amount)) @@ -110,7 +111,7 @@ impl OperatorDelegation { } pub fn slash(&mut self, slash_amount: u64) -> VaultCoreResult<()> { - let total_security_amount = self.delegated_security()?; + let total_security_amount = self.total_security()?; if slash_amount > total_security_amount { return Err(VaultCoreError::VaultSlashingUnderflow); } @@ -152,6 +153,7 @@ impl OperatorDelegation { Ok(()) } + /// Undelegates assets from the operator, pulling from the staked assets. pub fn undelegate(&mut self, amount: u64) -> VaultCoreResult<()> { self.staked_amount = self .staked_amount @@ -165,11 +167,15 @@ impl OperatorDelegation { Ok(()) } - /// Un-delegates assets for withdraw from the operator. If the total amount to withdraw is greater - /// than the staked amount, it pulls from the enqueued_for_cooldown_amount. If there is still excess, - /// it pulls from the cooling_down_amount. + /// Un-delegates assets for withdraw from the operator. If the total amount to withdraw is + /// greater than the staked amount, it pulls from the enqueued_for_cooldown_amount. + /// If there is still excess, it pulls from the cooling_down_amount. + /// + /// Funds that are cooling down are likely meant to be re-delegated by the delegation manager. + /// The function first withdraws from staked assets, falling back to cooling down assets + /// to avoid blocking the delegation manager from redelegating. pub fn undelegate_for_withdraw(&mut self, amount: u64) -> VaultCoreResult<()> { - if amount > self.amount_available_to_withdraw()? { + if amount > self.withdrawable_security()? { return Err(VaultCoreError::VaultDelegationListInsufficientSecurity); } @@ -228,7 +234,7 @@ mod tests { let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); operator_delegation.delegate(100).unwrap(); assert_eq!(operator_delegation.staked_amount(), 100); - assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + assert_eq!(operator_delegation.total_security().unwrap(), 100); } #[test] @@ -240,21 +246,21 @@ mod tests { assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 50); assert_eq!(operator_delegation.cooling_down_amount(), 0); - assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + assert_eq!(operator_delegation.total_security().unwrap(), 100); assert_eq!(operator_delegation.update(), 0); assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 0); assert_eq!(operator_delegation.cooling_down_amount(), 50); - assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + assert_eq!(operator_delegation.total_security().unwrap(), 100); assert_eq!(operator_delegation.update(), 0); assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 0); assert_eq!(operator_delegation.cooling_down_amount(), 0); - assert_eq!(operator_delegation.delegated_security().unwrap(), 50); + assert_eq!(operator_delegation.total_security().unwrap(), 50); } #[test] @@ -266,21 +272,21 @@ mod tests { assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 50); assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 0); - assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + assert_eq!(operator_delegation.total_security().unwrap(), 100); assert_eq!(operator_delegation.update(), 0); assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 0); assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 50); - assert_eq!(operator_delegation.delegated_security().unwrap(), 100); + assert_eq!(operator_delegation.total_security().unwrap(), 100); assert_eq!(operator_delegation.update(), 50); assert_eq!(operator_delegation.staked_amount(), 50); assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 0); assert_eq!(operator_delegation.cooling_down_for_withdraw_amount(), 0); - assert_eq!(operator_delegation.delegated_security().unwrap(), 50); + assert_eq!(operator_delegation.total_security().unwrap(), 50); } #[test] @@ -290,7 +296,7 @@ mod tests { operator_delegation.undelegate(10_000).unwrap(); operator_delegation.slash(5_000).unwrap(); - assert_eq!(operator_delegation.delegated_security().unwrap(), 95_000); + assert_eq!(operator_delegation.total_security().unwrap(), 95_000); assert_eq!(operator_delegation.staked_amount(), 85_500); } diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 85188700..4ddc22fe 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -28,9 +28,8 @@ pub struct VaultDelegationList { /// the list of delegations delegations: Vec, - /// The amount withdrawable by valid tickets at any given point in time. This amount is set aside - /// and can't be delegated because there's a withdraw ticket with a claim on it. - amount_withdrawable_by_tickets: u64, + /// The reserve for withdrawable tokens + withdrawable_reserve_amount: u64, /// The last slot the operator list was updated. /// Delegation information here is out of date if the last update epoch < current epoch @@ -49,7 +48,7 @@ impl VaultDelegationList { account_type: AccountType::VaultDelegationList, vault, delegations: vec![], - amount_withdrawable_by_tickets: 0, + withdrawable_reserve_amount: 0, last_slot_updated: 0, reserved: [0; 128], bump, @@ -68,29 +67,46 @@ impl VaultDelegationList { &self.delegations } - pub const fn amount_withdrawable_by_tickets(&self) -> u64 { - self.amount_withdrawable_by_tickets + pub const fn withdrawable_reserve_amount(&self) -> u64 { + self.withdrawable_reserve_amount } - pub fn decrement_amount_withdrawable_by_tickets(&mut self, amount: u64) -> VaultCoreResult<()> { - self.amount_withdrawable_by_tickets = self - .amount_withdrawable_by_tickets + pub fn decrement_withdrawable_reserve_amount(&mut self, amount: u64) -> VaultCoreResult<()> { + self.withdrawable_reserve_amount = self + .withdrawable_reserve_amount .checked_sub(amount) .ok_or(VaultCoreError::VaultDelegationListAmountWithdrawableUnderflow)?; Ok(()) } - /// Returns the total active + cooling down delegations - pub fn delegated_security(&self) -> VaultCoreResult { + /// Returns the total security in the delegation list + pub fn all_security(&self) -> VaultCoreResult { let mut total: u64 = 0; for operator in self.delegations.iter() { total = total - .checked_add(operator.delegated_security()?) + .checked_add(operator.total_security()?) .ok_or(VaultCoreError::VaultDelegationListTotalDelegationOverflow)?; } Ok(total) } + /// The amount of security available for withdrawal from the delegation list. Includes + /// staked and assets cooling down that aren't set aside for the withdrawal reserve + pub fn withdrawable_security(&self) -> VaultCoreResult { + let mut total: u64 = 0; + for operator in self.delegations.iter() { + total = total + .checked_add(operator.withdrawable_security()?) + .ok_or(VaultCoreError::VaultDelegationListTotalDelegationOverflow)?; + } + Ok(total) + } + + /// Checks to see if the vault needs updating, which is defined as the epoch of the last update + /// slot being less than the current epoch. + /// + /// # Returns + /// true if the vault delegation list needs updating, false if not. #[inline(always)] pub fn is_update_needed(&self, slot: u64, epoch_length: u64) -> bool { let last_updated_epoch = self.last_slot_updated.checked_div(epoch_length).unwrap(); @@ -120,8 +136,8 @@ impl VaultDelegationList { 1 => { // enqueued -> cooling down, enqueued wiped for operator in self.delegations.iter_mut() { - self.amount_withdrawable_by_tickets = self - .amount_withdrawable_by_tickets + self.withdrawable_reserve_amount = self + .withdrawable_reserve_amount .checked_add(operator.update()) .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; } @@ -133,11 +149,10 @@ impl VaultDelegationList { let amount_withdrawal_1 = operator.update(); let amount_withdrawal_2 = operator.update(); - self.amount_withdrawable_by_tickets = self - .amount_withdrawable_by_tickets + self.withdrawable_reserve_amount = self + .withdrawable_reserve_amount .checked_add(amount_withdrawal_1) - .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)? - .checked_add(amount_withdrawal_2) + .and_then(|x| x.checked_add(amount_withdrawal_2)) .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; } } @@ -161,14 +176,12 @@ impl VaultDelegationList { amount: u64, total_deposited: u64, ) -> VaultCoreResult<()> { - let delegated_security = self.delegated_security()?; + let delegated_security = self.all_security()?; // Ensure the amount delegated doesn't exceed the total deposited let security_available_for_delegation = total_deposited .checked_sub(delegated_security) - .and_then(|assets_available_to_delegate| { - assets_available_to_delegate.checked_sub(self.amount_withdrawable_by_tickets) - }) + .and_then(|x| x.checked_sub(self.withdrawable_reserve_amount)) .ok_or(VaultCoreError::VaultDelegationListInsufficientSecurity)?; if amount > security_available_for_delegation { @@ -202,22 +215,21 @@ impl VaultDelegationList { } } - /// TODO (LB): what happens if try to undelegate more than total delegated? Must mean some are set - /// aside that haven't been delegated yet? fn undelegate_for_withdraw_pro_rata(&mut self, amount: u64) -> VaultCoreResult<()> { - let total_delegated = self.delegated_security()?; + let withdrawable_assets = self.withdrawable_security()?; - if amount > total_delegated || total_delegated == 0 { + if amount > withdrawable_assets || withdrawable_assets == 0 { return Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds); } let mut remaining_to_undelegate = amount; for delegation in self.delegations.iter_mut() { - let delegated_security = delegation.delegated_security()?; + // TODO (LB): instead of pro-rata for all stake, should be pro-rata for withdrawable stake + let delegated_security = delegation.withdrawable_security()?; let undelegate_amount = (delegated_security as u128) .checked_mul(amount as u128) - .and_then(|product| product.checked_div(total_delegated as u128)) + .and_then(|product| product.checked_div(withdrawable_assets as u128)) .and_then(|result| result.try_into().ok()) .ok_or(VaultCoreError::ArithmeticOverflow)?; @@ -391,13 +403,13 @@ mod tests { let operator = Pubkey::new_unique(); assert!(list.delegate(operator, 100, 1_000).is_ok()); - assert_eq!(list.delegated_security().unwrap(), 100); + assert_eq!(list.all_security().unwrap(), 100); assert_eq!(list.delegations().len(), 1); let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.operator(), operator); assert_eq!(delegation.staked_amount(), 100); - assert_eq!(delegation.delegated_security().unwrap(), 100); + assert_eq!(delegation.total_security().unwrap(), 100); } #[test] @@ -406,7 +418,7 @@ mod tests { let operator = Pubkey::new_unique(); list.delegate(operator, 100, 1_000).unwrap(); list.delegate(operator, 50, 1_000).unwrap(); - assert_eq!(list.delegated_security().unwrap(), 150); + assert_eq!(list.all_security().unwrap(), 150); assert_eq!(list.delegations().len(), 1); let delegation = list.delegations().get(0).unwrap(); @@ -426,7 +438,7 @@ mod tests { let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 70); assert_eq!(delegation.enqueued_for_cooldown_amount(), 30); - assert_eq!(delegation.delegated_security().unwrap(), 100); + assert_eq!(delegation.total_security().unwrap(), 100); } #[test] @@ -439,7 +451,7 @@ mod tests { let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 80); - assert_eq!(delegation.delegated_security().unwrap(), 80); + assert_eq!(delegation.total_security().unwrap(), 80); } #[test] @@ -467,7 +479,7 @@ mod tests { let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 70); assert_eq!(delegation.cooling_down_amount(), 0); - assert_eq!(list.amount_withdrawable_by_tickets(), 0); + assert_eq!(list.withdrawable_reserve_amount(), 0); } #[test] @@ -491,13 +503,13 @@ mod tests { initial_delegation - undelegate_amount ); assert_eq!(delegation.enqueued_for_withdraw_amount(), undelegate_amount); - assert_eq!(list.amount_withdrawable_by_tickets(), 0); + assert_eq!(list.withdrawable_reserve_amount(), 0); assert!(list.update(100, 100).unwrap()); assert!(list.update(200, 100).unwrap()); - assert_eq!(list.amount_withdrawable_by_tickets(), undelegate_amount); + assert_eq!(list.withdrawable_reserve_amount(), undelegate_amount); assert_eq!( - list.delegated_security().unwrap(), + list.all_security().unwrap(), initial_delegation - undelegate_amount ); @@ -534,14 +546,14 @@ mod tests { list.delegate(operator2, 1500, 3000).unwrap(); list.delegate(operator3, 500, 3000).unwrap(); - let total_delegated_before_undelegation = list.delegated_security().unwrap(); + let total_delegated_before_undelegation = list.all_security().unwrap(); list.undelegate_for_withdraw(600, UndelegateForWithdrawMethod::ProRata) .unwrap(); assert_eq!( total_delegated_before_undelegation, - list.delegated_security().unwrap() + list.all_security().unwrap() ); // 3000 total staked, 600 withdrawn diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs index 06c46647..1a5cf065 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -76,10 +76,10 @@ pub fn process_burn_withdraw_ticket( let tokens_deposited_in_vault = vault.vault().tokens_deposited(); let delegated_security_in_vault = vault_delegation_list .vault_delegation_list() - .delegated_security()?; + .all_security()?; let assets_reserved_for_withdraw_tickets = vault_delegation_list .vault_delegation_list() - .amount_withdrawable_by_tickets(); + .withdrawable_reserve_amount(); let available_unstaked_assets = tokens_deposited_in_vault .checked_sub(delegated_security_in_vault) @@ -135,7 +135,7 @@ pub fn process_burn_withdraw_ticket( // Decrement the amount reserved for withdraw tickets because it's been claimed now vault_delegation_list .vault_delegation_list_mut() - .decrement_amount_withdrawable_by_tickets(original_redemption_amount)?; + .decrement_withdrawable_reserve_amount(original_redemption_amount)?; // refresh after burn vault From c74458f409d66bf200b040bcb80b7cc73a9f5f2c Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 18:38:39 -0500 Subject: [PATCH 11/22] more tests around undelegating for cooldown --- .../tests/fixtures/vault_client.rs | 54 +----------- .../tests/vault/enqueue_withdrawal.rs | 1 - integration_tests/tests/vault/slash.rs | 2 - vault_core/src/operator_delegation.rs | 74 ++++++++++++++++ vault_core/src/vault_delegation_list.rs | 87 ++++++++++++++++--- vault_program/src/enqueue_withdrawal.rs | 2 +- 6 files changed, 153 insertions(+), 67 deletions(-) diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 25aa0235..c35b3a31 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -27,10 +27,7 @@ use solana_sdk::{ use spl_associated_token_account::{ get_associated_token_address, instruction::create_associated_token_account_idempotent, }; -use spl_token::{ - instruction::initialize_mint2, - state::{Account, Mint}, -}; +use spl_token::{instruction::initialize_mint2, state::Mint}; pub struct VaultRoot { pub vault_pubkey: Pubkey, @@ -923,55 +920,6 @@ impl VaultProgramClient { .await } - pub async fn get_token_account( - &mut self, - token_account: &Pubkey, - ) -> Result { - let account = self - .banks_client - .get_account(*token_account) - .await? - .unwrap(); - Ok(Account::unpack(&account.data).unwrap()) - } - - /// Mints tokens to an ATA owned by the `to` address - pub async fn mint_spl_to( - &mut self, - mint: &Pubkey, - to: &Pubkey, - amount: u64, - ) -> Result<(), BanksClientError> { - let blockhash = self.banks_client.get_latest_blockhash().await?; - self.banks_client - .process_transaction_with_preflight_and_commitment( - Transaction::new_signed_with_payer( - &[ - create_associated_token_account_idempotent( - &self.payer.pubkey(), - to, - mint, - &spl_token::id(), - ), - spl_token::instruction::mint_to( - &spl_token::id(), - mint, - &get_associated_token_address(to, mint), - &self.payer.pubkey(), - &[], - amount, - ) - .unwrap(), - ], - Some(&self.payer.pubkey()), - &[&self.payer], - blockhash, - ), - CommitmentLevel::Processed, - ) - .await - } - async fn _create_token_mint(&mut self, mint: &Keypair) -> Result<(), BanksClientError> { let blockhash = self.banks_client.get_latest_blockhash().await?; let rent: Rent = self.banks_client.get_sysvar().await?; diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index 7c51ec02..4e7d587c 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,4 +1,3 @@ -use jito_vault_core::vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket; use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index 43d28d23..82946f34 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -120,8 +120,6 @@ async fn test_slash_ok() { .create_ata(&vault.supported_mint(), &slasher.pubkey()) .await .unwrap(); - let slasher_token_account = - get_associated_token_address(&slasher.pubkey(), &vault.supported_mint()); vault_program_client .setup_vault_avs_slasher_operator_ticket( diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 4f9230e8..1e380403 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -332,4 +332,78 @@ mod tests { .undelegate_for_withdraw(50_001) .unwrap_err(); } + + /// Test pulling assets from enqueued for cooling down after staked assets are exhausted + #[test] + fn test_undelegate_for_withdraw_pull_from_enqueued_for_cooling_down() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + + operator_delegation.delegate(100_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + + operator_delegation.undelegate(50_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 50_000); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 50_000); + + // shall pull 50,000 from the staked and 10,000 from the undelegated + operator_delegation.undelegate_for_withdraw(60_000).unwrap(); + + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 0); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 60_000); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 40_000); + } + + /// Test pulling assets from cooling down after staked assets are exhausted + #[test] + fn test_undelegate_for_withdraw_pull_from_cooling_down() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + + operator_delegation.delegate(100_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + + operator_delegation.undelegate(50_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 50_000); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 50_000); + + assert_eq!(operator_delegation.update(), 0); + + // shall pull 50,000 from the staked and 10,000 from the undelegated + operator_delegation.undelegate_for_withdraw(60_000).unwrap(); + + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 0); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 60_000); + assert_eq!(operator_delegation.cooling_down_amount(), 40_000); + } + + #[test] + fn test_undelegate_for_withdraw_pull_from_enqueued_for_cooling_down_and_cooling_down() { + let mut operator_delegation = OperatorDelegation::new(Pubkey::new_unique()); + + operator_delegation.delegate(100_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + + operator_delegation.undelegate(50_000).unwrap(); + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 50_000); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 50_000); + + assert_eq!(operator_delegation.update(), 0); + + operator_delegation.undelegate(10_000).unwrap(); + + // 100k total security, 40k staked, 10k in enqueued for cooling down, 50k in cooling down + + operator_delegation.undelegate_for_withdraw(60_000).unwrap(); + // shall pull 40,000 from the staked, 10k from the enqueued for cooling down, and 10k from cooling down + + assert_eq!(operator_delegation.total_security().unwrap(), 100_000); + assert_eq!(operator_delegation.staked_amount(), 0); + assert_eq!(operator_delegation.enqueued_for_cooldown_amount(), 0); + assert_eq!(operator_delegation.cooling_down_amount(), 40_000); + assert_eq!(operator_delegation.enqueued_for_withdraw_amount(), 60_000); + } } diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 4ddc22fe..6691c664 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -205,17 +205,19 @@ impl VaultDelegationList { } /// Undelegates an amount of stake from the vault for withdrawal - pub fn undelegate_for_withdraw( + pub fn undelegate_for_withdrawal( &mut self, amount: u64, method: UndelegateForWithdrawMethod, ) -> VaultCoreResult<()> { match method { - UndelegateForWithdrawMethod::ProRata => self.undelegate_for_withdraw_pro_rata(amount), + UndelegateForWithdrawMethod::ProRata => self.undelegate_for_withdrawal_pro_rata(amount), } } - fn undelegate_for_withdraw_pro_rata(&mut self, amount: u64) -> VaultCoreResult<()> { + /// Un-delegates `amount` staked assets from all the operators pro-rata based on the withdrawable + /// security on each one. + fn undelegate_for_withdrawal_pro_rata(&mut self, amount: u64) -> VaultCoreResult<()> { let withdrawable_assets = self.withdrawable_security()?; if amount > withdrawable_assets || withdrawable_assets == 0 { @@ -493,7 +495,7 @@ mod tests { list.delegate(operator, initial_delegation, total_deposited) .unwrap(); - list.undelegate_for_withdraw(undelegate_amount, UndelegateForWithdrawMethod::ProRata) + list.undelegate_for_withdrawal(undelegate_amount, UndelegateForWithdrawMethod::ProRata) .unwrap(); assert_eq!(list.delegations().len(), 1); @@ -527,7 +529,7 @@ mod tests { let operator = Pubkey::new_unique(); list.delegate(operator, 1000, 1000).unwrap(); - list.undelegate_for_withdraw(500, UndelegateForWithdrawMethod::ProRata) + list.undelegate_for_withdrawal(500, UndelegateForWithdrawMethod::ProRata) .unwrap(); let delegation = list.delegations().get(0).unwrap(); @@ -548,7 +550,7 @@ mod tests { let total_delegated_before_undelegation = list.all_security().unwrap(); - list.undelegate_for_withdraw(600, UndelegateForWithdrawMethod::ProRata) + list.undelegate_for_withdrawal(600, UndelegateForWithdrawMethod::ProRata) .unwrap(); assert_eq!( @@ -577,7 +579,7 @@ mod tests { list.delegate(operator2, 100, 301).unwrap(); list.delegate(operator3, 101, 301).unwrap(); - list.undelegate_for_withdraw(100, UndelegateForWithdrawMethod::ProRata) + list.undelegate_for_withdrawal(100, UndelegateForWithdrawMethod::ProRata) .unwrap(); let delegations = list.delegations(); @@ -592,7 +594,7 @@ mod tests { let operator = Pubkey::new_unique(); list.delegate(operator, 100, 100).unwrap(); - let result = list.undelegate_for_withdraw(101, UndelegateForWithdrawMethod::ProRata); + let result = list.undelegate_for_withdrawal(101, UndelegateForWithdrawMethod::ProRata); assert!(matches!( result, Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds) @@ -603,7 +605,7 @@ mod tests { fn test_undelegate_for_withdraw_pro_rata_no_delegations() { let mut list = setup_vault_delegation_list(); - let result = list.undelegate_for_withdraw(100, UndelegateForWithdrawMethod::ProRata); + let result = list.undelegate_for_withdrawal(100, UndelegateForWithdrawMethod::ProRata); assert!(matches!( result, Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds) @@ -616,11 +618,76 @@ mod tests { let operator = Pubkey::new_unique(); list.delegate(operator, 100, 100).unwrap(); - list.undelegate_for_withdraw(0, UndelegateForWithdrawMethod::ProRata) + list.undelegate_for_withdrawal(0, UndelegateForWithdrawMethod::ProRata) .unwrap(); let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 100); assert_eq!(delegation.enqueued_for_withdraw_amount(), 0); } + + // ensures cooling down assets are handled correctly when undelegating + #[test] + fn test_undelegate_for_withdraw_with_cooling_down_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 10_000, total_deposited).unwrap(); + + let operator_2 = Pubkey::new_unique(); + list.delegate(operator_2, 60_000, total_deposited).unwrap(); + + let operator_3 = Pubkey::new_unique(); + list.delegate(operator_3, 30_000, total_deposited).unwrap(); + + list.undelegate(operator_2, 30_000).unwrap(); + + assert_eq!(list.all_security().unwrap(), total_deposited); + + let delegation_1 = list.delegations().get(0).unwrap(); + assert_eq!(delegation_1.operator(), operator_1); + assert_eq!(delegation_1.withdrawable_security().unwrap(), 10_000); + + let delegation_2 = list.delegations().get(1).unwrap(); + assert_eq!(delegation_2.operator(), operator_2); + assert_eq!(delegation_2.withdrawable_security().unwrap(), 60_000); + assert_eq!(delegation_2.staked_amount(), 30_000); + assert_eq!(delegation_2.enqueued_for_cooldown_amount(), 30_000); + + let delegation_3 = list.delegations().get(2).unwrap(); + assert_eq!(delegation_3.operator(), operator_3); + assert_eq!(delegation_3.withdrawable_security().unwrap(), 30_000); + + list.undelegate_for_withdrawal(50_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + // 10% of assets staked -> 10% of withdraw + let delegation_1 = list.delegations().get(0).unwrap(); + assert_eq!(delegation_1.operator(), operator_1); + assert_eq!(delegation_1.total_security().unwrap(), 10_000); + assert_eq!(delegation_1.enqueued_for_withdraw_amount(), 5_000); + + // 30k was staked, 30k was cooling down + // 60% of assets staked -> 60% of withdraw -> 30,000 withdrawn + let delegation_2 = list.delegations().get(1).unwrap(); + assert_eq!(delegation_2.total_security().unwrap(), 60_000); + assert_eq!(delegation_2.operator(), operator_2); + assert_eq!(delegation_2.staked_amount(), 0); + assert_eq!(delegation_2.enqueued_for_withdraw_amount(), 30_000); + assert_eq!(delegation_2.enqueued_for_cooldown_amount(), 30_000); + + // 30% of assets staked -> 30% of withdraw + let delegation_3 = list.delegations().get(2).unwrap(); + assert_eq!(delegation_3.total_security().unwrap(), 30_000); + assert_eq!(delegation_3.operator(), operator_3); + assert_eq!(delegation_3.enqueued_for_withdraw_amount(), 15_000); + } + + #[test] + fn test_undelegate_for_withdraw_pull_from_enqueued_for_cooling_down() {} + + /// ensures that assets cooling down for withdraw are handled correctly when undelegating + #[test] + fn test_undelegate_for_withdraw_with_cooling_down_for_withdrawal_assets() {} } diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdrawal.rs index 17b7ef70..ff360be4 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -85,7 +85,7 @@ pub fn process_enqueue_withdrawal( vault_delegation_list .vault_delegation_list_mut() - .undelegate_for_withdraw(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; + .undelegate_for_withdrawal(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; _create_vault_staker_withdraw_ticket( program_id, From 7421c0c5427bb1533b320459055b1ec214510a57 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 19:54:26 -0500 Subject: [PATCH 12/22] more undelegation testing --- vault_core/src/vault_delegation_list.rs | 94 ++++++++++++++++++------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 6691c664..8ce9c1a9 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -224,37 +224,34 @@ impl VaultDelegationList { return Err(VaultCoreError::WithdrawAmountExceedsDelegatedFunds); } - let mut remaining_to_undelegate = amount; + let mut total_undelegated: u64 = 0; for delegation in self.delegations.iter_mut() { - // TODO (LB): instead of pro-rata for all stake, should be pro-rata for withdrawable stake let delegated_security = delegation.withdrawable_security()?; - let undelegate_amount = (delegated_security as u128) - .checked_mul(amount as u128) - .and_then(|product| product.checked_div(withdrawable_assets as u128)) - .and_then(|result| result.try_into().ok()) - .ok_or(VaultCoreError::ArithmeticOverflow)?; + + // Calculate undelegate amount using div_ceil + let undelegate_amount = delegated_security + .checked_mul(amount) + .ok_or(VaultCoreError::ArithmeticOverflow)? + .div_ceil(withdrawable_assets); if undelegate_amount > 0 { - delegation.undelegate_for_withdraw(undelegate_amount)?; - remaining_to_undelegate = remaining_to_undelegate - .checked_sub(undelegate_amount) - .ok_or(VaultCoreError::ArithmeticUnderflow)?; - } - } + let actual_undelegate = + std::cmp::min(undelegate_amount, amount.saturating_sub(total_undelegated)); + + delegation.undelegate_for_withdraw(actual_undelegate)?; - // Handle any remaining dust due to rounding - if remaining_to_undelegate > 0 { - for delegation in self.delegations.iter_mut() { - if delegation.staked_amount() >= remaining_to_undelegate { - delegation.undelegate_for_withdraw(remaining_to_undelegate)?; - remaining_to_undelegate = 0; + total_undelegated = total_undelegated + .checked_add(actual_undelegate) + .ok_or(VaultCoreError::ArithmeticOverflow)?; + + if total_undelegated == amount { break; } } } - if remaining_to_undelegate > 0 { + if total_undelegated != amount { return Err(VaultCoreError::UndelegationIncomplete); } @@ -584,8 +581,8 @@ mod tests { let delegations = list.delegations(); assert_eq!(delegations[0].enqueued_for_withdraw_amount(), 34); - assert_eq!(delegations[1].enqueued_for_withdraw_amount(), 33); - assert_eq!(delegations[2].enqueued_for_withdraw_amount(), 33); + assert_eq!(delegations[1].enqueued_for_withdraw_amount(), 34); + assert_eq!(delegations[2].enqueued_for_withdraw_amount(), 32); } #[test] @@ -628,7 +625,7 @@ mod tests { // ensures cooling down assets are handled correctly when undelegating #[test] - fn test_undelegate_for_withdraw_with_cooling_down_assets() { + fn test_undelegate_for_withdraw_with_enqueued_for_cooling_down_assets() { let mut list = setup_vault_delegation_list(); let total_deposited = 100_000; @@ -684,10 +681,53 @@ mod tests { assert_eq!(delegation_3.enqueued_for_withdraw_amount(), 15_000); } - #[test] - fn test_undelegate_for_withdraw_pull_from_enqueued_for_cooling_down() {} - /// ensures that assets cooling down for withdraw are handled correctly when undelegating #[test] - fn test_undelegate_for_withdraw_with_cooling_down_for_withdrawal_assets() {} + fn test_undelegate_for_withdraw_with_cooling_down_for_withdrawal_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 10_000, total_deposited).unwrap(); + + let operator_2 = Pubkey::new_unique(); + list.delegate(operator_2, 60_000, total_deposited).unwrap(); + + let operator_3 = Pubkey::new_unique(); + list.delegate(operator_3, 25_000, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(30_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + // 10000 * 30000 /_ceil 95000 + let delegation_1 = list.delegations.get(0).unwrap(); + assert_eq!(delegation_1.enqueued_for_withdraw_amount(), 3_158); + + // 60000 * 30000 /_ceil 95000 + let delegation_2 = list.delegations.get(1).unwrap(); + assert_eq!(delegation_2.enqueued_for_withdraw_amount(), 18_948); + + // min(25000 * 30000 /_ceil 95000, 30000 - 18948 - 3158) + let delegation_3 = list.delegations.get(2).unwrap(); + assert_eq!(delegation_3.enqueued_for_withdraw_amount(), 7_894); + + // send 5k more to operator 3 + list.delegate(operator_3, 5_000, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(20_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + // 3158 + ((10000 - 3158) * 20000 /_ceil 70000) + let delegation_1 = list.delegations.get(0).unwrap(); + assert_eq!(delegation_1.enqueued_for_withdraw_amount(), 5113); + + // 18948 + ((60000 - 18948) * 20000 /_ceil 70000) + let delegation_2 = list.delegations.get(1).unwrap(); + assert_eq!(delegation_2.enqueued_for_withdraw_amount(), 30678); + + // minimum of 7894 + ((30000 - 7894) * 20000 /_ceil 70000) and whatever is left to get to 50000 total + // undelegated + let delegation_3 = list.delegations.get(2).unwrap(); + assert_eq!(delegation_3.enqueued_for_withdraw_amount(), 14209); + } } From f1b00532947a90eb048d23bd87aa5abf238eafad Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 20:02:43 -0500 Subject: [PATCH 13/22] cleanup --- vault_core/src/vault_delegation_list.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 8ce9c1a9..6b64493a 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -229,13 +229,14 @@ impl VaultDelegationList { for delegation in self.delegations.iter_mut() { let delegated_security = delegation.withdrawable_security()?; - // Calculate undelegate amount using div_ceil + // Calculate un-delegate amount, rounding up let undelegate_amount = delegated_security .checked_mul(amount) .ok_or(VaultCoreError::ArithmeticOverflow)? .div_ceil(withdrawable_assets); if undelegate_amount > 0 { + // don't un-delegate too much let actual_undelegate = std::cmp::min(undelegate_amount, amount.saturating_sub(total_undelegated)); @@ -730,4 +731,26 @@ mod tests { let delegation_3 = list.delegations.get(2).unwrap(); assert_eq!(delegation_3.enqueued_for_withdraw_amount(), 14209); } + + #[test] + fn test_undelegate_for_withdraw_ceiling_ok() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 1, total_deposited).unwrap(); + + let operator_2 = Pubkey::new_unique(); + list.delegate(operator_2, 99_999, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(99_999, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + // 1 * 99999 / 100000 + let delegation_1 = list.delegations().get(0).unwrap(); + assert_eq!(delegation_1.enqueued_for_withdraw_amount(), 1); + + let delegation_2 = list.delegations().get(1).unwrap(); + assert_eq!(delegation_2.enqueued_for_withdraw_amount(), 99_998); + } } From c61dc6c29be518ebafdc013e446339b60844df0a Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 20:05:40 -0500 Subject: [PATCH 14/22] werd --- .../tests/fixtures/vault_client.rs | 30 +++--- .../tests/vault/enqueue_withdrawal.rs | 16 +-- vault_core/src/lib.rs | 4 +- vault_core/src/result.rs | 18 ++-- ...t.rs => vault_staker_withdrawal_ticket.rs} | 53 +++++----- vault_program/src/burn_withdraw_ticket.rs | 97 ++++++++++--------- vault_program/src/enqueue_withdrawal.rs | 69 ++++++------- vault_sdk/src/lib.rs | 16 +-- 8 files changed, 156 insertions(+), 147 deletions(-) rename vault_core/src/{vault_staker_withdraw_ticket.rs => vault_staker_withdrawal_ticket.rs} (71%) diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index c35b3a31..e8618751 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -8,7 +8,7 @@ use jito_vault_core::{ config::Config, vault::Vault, vault_avs_slasher_operator_ticket::VaultAvsSlasherOperatorTicket, vault_avs_slasher_ticket::VaultAvsSlasherTicket, vault_avs_ticket::VaultAvsTicket, vault_delegation_list::VaultDelegationList, vault_operator_ticket::VaultOperatorTicket, - vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket, + vault_staker_withdrawal_ticket::VaultStakerWithdrawalTicket, }; use jito_vault_sdk::{add_delegation, initialize_config, initialize_vault}; use solana_program::{ @@ -35,7 +35,7 @@ pub struct VaultRoot { } #[derive(Debug)] -pub struct VaultStakerWithdrawTicketRoot { +pub struct VaultStakerWithdrawalTicketRoot { pub base: Pubkey, } @@ -97,7 +97,7 @@ impl VaultProgramClient { )?) } - pub async fn get_vault_staker_withdraw_ticket( + pub async fn get_vault_staker_withdrawal_ticket( &mut self, vault: &Pubkey, staker: &Pubkey, @@ -612,23 +612,23 @@ impl VaultProgramClient { vault_root: &VaultRoot, depositor: &Keypair, amount: u64, - ) -> Result { + ) -> Result { let vault = self.get_vault(&vault_root.vault_pubkey).await.unwrap(); let depositor_lrt_token_account = get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); let base = Keypair::new(); - let vault_staker_withdraw_ticket = VaultStakerWithdrawalTicket::find_program_address( + let vault_staker_withdrawal_ticket = VaultStakerWithdrawalTicket::find_program_address( &jito_vault_program::id(), &vault_root.vault_pubkey, &depositor.pubkey(), &base.pubkey(), ) .0; - let vault_staker_withdraw_ticket_token_account = - get_associated_token_address(&vault_staker_withdraw_ticket, &vault.lrt_mint()); + let vault_staker_withdrawal_ticket_token_account = + get_associated_token_address(&vault_staker_withdrawal_ticket, &vault.lrt_mint()); - self.create_ata(&vault.lrt_mint(), &vault_staker_withdraw_ticket) + self.create_ata(&vault.lrt_mint(), &vault_staker_withdrawal_ticket) .await?; let vault_staker_fee_token_account = @@ -642,8 +642,8 @@ impl VaultProgramClient { &vault_root.vault_pubkey, ) .0, - &vault_staker_withdraw_ticket, - &vault_staker_withdraw_ticket_token_account, + &vault_staker_withdrawal_ticket, + &vault_staker_withdrawal_ticket_token_account, &vault_staker_fee_token_account, depositor, &depositor_lrt_token_account, @@ -652,7 +652,7 @@ impl VaultProgramClient { ) .await?; - Ok(VaultStakerWithdrawTicketRoot { + Ok(VaultStakerWithdrawalTicketRoot { base: base.pubkey(), }) } @@ -683,8 +683,8 @@ impl VaultProgramClient { config: &Pubkey, vault: &Pubkey, vault_delegation_list: &Pubkey, - vault_staker_withdraw_ticket: &Pubkey, - vault_staker_withdraw_ticket_token_account: &Pubkey, + vault_staker_withdrawal_ticket: &Pubkey, + vault_staker_withdrawal_ticket_token_account: &Pubkey, vault_fee_token_account: &Pubkey, staker: &Keypair, staker_lrt_token_account: &Pubkey, @@ -698,8 +698,8 @@ impl VaultProgramClient { config, vault, vault_delegation_list, - vault_staker_withdraw_ticket, - vault_staker_withdraw_ticket_token_account, + vault_staker_withdrawal_ticket, + vault_staker_withdrawal_ticket_token_account, vault_fee_token_account, &staker.pubkey(), staker_lrt_token_account, diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index 4e7d587c..498d533b 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,7 +1,7 @@ use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address; -use crate::fixtures::{fixture::TestBuilder, vault_client::VaultStakerWithdrawTicketRoot}; +use crate::fixtures::{fixture::TestBuilder, vault_client::VaultStakerWithdrawalTicketRoot}; #[tokio::test] async fn test_enqueue_withdraw_more_than_staked_fails() { @@ -166,7 +166,7 @@ async fn test_enqueue_withdraw_with_fee_success() { // the user is withdrawing 99,000 LRT tokens, there is a 1% fee on withdraws, so // 98010 tokens will be undeleged for withdraw - let VaultStakerWithdrawTicketRoot { base } = vault_program_client + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client .do_enqueue_withdraw(&vault_root, &depositor, 99_000) .await .unwrap(); @@ -183,13 +183,13 @@ async fn test_enqueue_withdraw_with_fee_success() { assert_eq!(delegation.enqueued_for_withdraw_amount(), 98_010); assert_eq!(delegation.total_security().unwrap(), 100_000); - let vault_staker_withdraw_ticket = vault_program_client - .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) + let vault_staker_withdrawal_ticket = vault_program_client + .get_vault_staker_withdrawal_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) .await .unwrap(); - assert_eq!(vault_staker_withdraw_ticket.lrt_amount(), 98_010); + assert_eq!(vault_staker_withdrawal_ticket.lrt_amount(), 98_010); assert_eq!( - vault_staker_withdraw_ticket.withdraw_allocation_amount(), + vault_staker_withdrawal_ticket.withdraw_allocation_amount(), 98_010 ); } @@ -285,14 +285,14 @@ async fn test_enqueue_withdraw_with_reward_ok() { // Enqueue withdrawal for half of the original deposit let withdraw_amount = 50_000; - let VaultStakerWithdrawTicketRoot { base } = vault_program_client + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client .do_enqueue_withdraw(&vault_root, &depositor, withdraw_amount) .await .unwrap(); // Verify the withdraw ticket let withdraw_ticket = vault_program_client - .get_vault_staker_withdraw_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) + .get_vault_staker_withdrawal_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) .await .unwrap(); diff --git a/vault_core/src/lib.rs b/vault_core/src/lib.rs index da9627b9..62865688 100644 --- a/vault_core/src/lib.rs +++ b/vault_core/src/lib.rs @@ -9,7 +9,7 @@ pub mod vault_avs_slasher_ticket; pub mod vault_avs_ticket; pub mod vault_delegation_list; pub mod vault_operator_ticket; -pub mod vault_staker_withdraw_ticket; +pub mod vault_staker_withdrawal_ticket; #[derive(Debug, Clone, Copy, PartialEq, BorshDeserialize, BorshSerialize)] #[repr(u32)] @@ -22,5 +22,5 @@ enum AccountType { VaultDelegationList, VaultAvsSlasherOperatorTicket, VaultStakerWithdrawalTicket, - VaultStakerWithdrawTicketEmpty, + VaultStakerWithdrawalTicketEmpty, } diff --git a/vault_core/src/result.rs b/vault_core/src/result.rs index c7163a82..3f93090d 100644 --- a/vault_core/src/result.rs +++ b/vault_core/src/result.rs @@ -89,13 +89,13 @@ pub enum VaultCoreError { VaultAvsSlasherOperatorNotWritable, VaultAvsSlasherOperatorOverflow, VaultAvsSlasherOperatorMaxSlashableExceeded, - VaultStakerWithdrawTicketEmpty, - VaultStakerWithdrawTicketEmptyInvalidOwner, - VaultStakerWithdrawTicketEmptyInvalidData(String), - VaultStakerWithdrawTicketEmptyInvalidAccountType, - VaultStakerWithdrawTicketEmptyInvalidPda, - VaultStakerWithdrawTicketInvalidProgramOwner, - VaultStakerWithdrawTicketNotWritable, + VaultStakerWithdrawalTicketEmpty, + VaultStakerWithdrawalTicketEmptyInvalidOwner, + VaultStakerWithdrawalTicketEmptyInvalidData(String), + VaultStakerWithdrawalTicketEmptyInvalidAccountType, + VaultStakerWithdrawalTicketEmptyInvalidPda, + VaultStakerWithdrawalTicketInvalidProgramOwner, + VaultStakerWithdrawalTicketNotWritable, VaultOperatorActiveStakeOverflow, VaultDelegationListUpdateOverflow, VaultDelegationListTotalDelegationOverflow, @@ -113,8 +113,8 @@ pub enum VaultCoreError { UndelegationIncomplete, VaultDelegationListAmountWithdrawableUnderflow, VaultDelegationListUpdateRequired, - VaultStakerWithdrawTicketOverflow, - VaultStakerWithdrawTicketNotWithdrawable, + VaultStakerWithdrawalTicketOverflow, + VaultStakerWithdrawalTicketNotWithdrawable, VaultUndelegationUnderflow, } diff --git a/vault_core/src/vault_staker_withdraw_ticket.rs b/vault_core/src/vault_staker_withdrawal_ticket.rs similarity index 71% rename from vault_core/src/vault_staker_withdraw_ticket.rs rename to vault_core/src/vault_staker_withdrawal_ticket.rs index 9b73412e..f0a50f57 100644 --- a/vault_core/src/vault_staker_withdraw_ticket.rs +++ b/vault_core/src/vault_staker_withdrawal_ticket.rs @@ -27,7 +27,7 @@ pub struct VaultStakerWithdrawalTicket { /// The amount of assets allocated for this staker's withdraw withdraw_allocation_amount: u64, - /// The amount of LRT held in the VaultStakerWithdrawTicket token account at the time of creation + /// The amount of LRT held in the VaultStakerWithdrawalTicket token account at the time of creation /// At first glance, this seems redundant, but it's necessary to prevent someone from depositing /// more LRT into the token account and skipping the withdraw queue. lrt_amount: u64, @@ -93,17 +93,17 @@ impl VaultStakerWithdrawalTicket { let epoch_unstaked = self.slot_unstaked.checked_div(epoch_length).unwrap(); if epoch_unstaked .checked_add(1) - .ok_or(VaultCoreError::VaultStakerWithdrawTicketOverflow)? + .ok_or(VaultCoreError::VaultStakerWithdrawalTicketOverflow)? < current_epoch { - return Err(VaultCoreError::VaultStakerWithdrawTicketNotWithdrawable); + return Err(VaultCoreError::VaultStakerWithdrawalTicketNotWithdrawable); } Ok(()) } pub fn seeds(vault: &Pubkey, staker: &Pubkey, base: &Pubkey) -> Vec> { Vec::from_iter([ - b"vault_staker_withdraw_ticket".to_vec(), + b"vault_staker_withdrawal_ticket".to_vec(), vault.to_bytes().to_vec(), staker.to_bytes().to_vec(), base.to_bytes().to_vec(), @@ -129,40 +129,41 @@ impl VaultStakerWithdrawalTicket { staker: &Pubkey, ) -> VaultCoreResult { if account.data_is_empty() { - return Err(VaultCoreError::VaultStakerWithdrawTicketEmpty); + return Err(VaultCoreError::VaultStakerWithdrawalTicketEmpty); } if account.owner != program_id { - return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidOwner); + return Err(VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidOwner); } - let vault_staker_withdraw_ticket = + let vault_staker_withdrawal_ticket = Self::deserialize(&mut account.data.borrow_mut().as_ref()).map_err(|e| { - VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidData(e.to_string()) + VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidData(e.to_string()) })?; - if vault_staker_withdraw_ticket.account_type != AccountType::VaultStakerWithdrawTicketEmpty + if vault_staker_withdrawal_ticket.account_type + != AccountType::VaultStakerWithdrawalTicketEmpty { - return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidAccountType); + return Err(VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidAccountType); } - let mut seeds = Self::seeds(vault, staker, &vault_staker_withdraw_ticket.base()); - seeds.push(vec![vault_staker_withdraw_ticket.bump]); + let mut seeds = Self::seeds(vault, staker, &vault_staker_withdrawal_ticket.base()); + seeds.push(vec![vault_staker_withdrawal_ticket.bump]); let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_ref()).collect(); let expected_pubkey = Pubkey::create_program_address(&seeds_iter, program_id) - .map_err(|_| VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidPda)?; + .map_err(|_| VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidPda)?; if expected_pubkey != *account.key { - return Err(VaultCoreError::VaultStakerWithdrawTicketEmptyInvalidPda); + return Err(VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidPda); } - Ok(vault_staker_withdraw_ticket) + Ok(vault_staker_withdrawal_ticket) } } -pub struct SanitizedVaultStakerWithdrawTicket<'a, 'info> { +pub struct SanitizedVaultStakerWithdrawalTicket<'a, 'info> { account: &'a AccountInfo<'info>, - vault_staker_withdraw_ticket: VaultStakerWithdrawalTicket, + vault_staker_withdrawal_ticket: VaultStakerWithdrawalTicket, } -impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { +impl<'a, 'info> SanitizedVaultStakerWithdrawalTicket<'a, 'info> { pub fn sanitize( program_id: &Pubkey, account: &'a AccountInfo<'info>, @@ -171,24 +172,24 @@ impl<'a, 'info> SanitizedVaultStakerWithdrawTicket<'a, 'info> { expected_writable: bool, ) -> VaultCoreResult { if expected_writable && !account.is_writable { - return Err(VaultCoreError::VaultStakerWithdrawTicketNotWritable); + return Err(VaultCoreError::VaultStakerWithdrawalTicketNotWritable); } - let vault_staker_withdraw_ticket = + let vault_staker_withdrawal_ticket = VaultStakerWithdrawalTicket::deserialize_checked(program_id, account, vault, staker)?; - Ok(SanitizedVaultStakerWithdrawTicket { + Ok(SanitizedVaultStakerWithdrawalTicket { account, - vault_staker_withdraw_ticket, + vault_staker_withdrawal_ticket, }) } - pub const fn vault_staker_withdraw_ticket(&self) -> &VaultStakerWithdrawalTicket { - &self.vault_staker_withdraw_ticket + pub const fn vault_staker_withdrawal_ticket(&self) -> &VaultStakerWithdrawalTicket { + &self.vault_staker_withdrawal_ticket } - pub fn vault_staker_withdraw_ticket_mut(&mut self) -> &mut VaultStakerWithdrawalTicket { - &mut self.vault_staker_withdraw_ticket + pub fn vault_staker_withdrawal_ticket_mut(&mut self) -> &mut VaultStakerWithdrawalTicket { + &mut self.vault_staker_withdrawal_ticket } pub const fn account(&self) -> &AccountInfo<'info> { diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdraw_ticket.rs index 1a5cf065..4404656b 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdraw_ticket.rs @@ -7,8 +7,8 @@ use jito_vault_core::{ config::SanitizedConfig, vault::{SanitizedVault, Vault}, vault_delegation_list::SanitizedVaultDelegationList, - vault_staker_withdraw_ticket::{ - SanitizedVaultStakerWithdrawTicket, VaultStakerWithdrawalTicket, + vault_staker_withdrawal_ticket::{ + SanitizedVaultStakerWithdrawalTicket, VaultStakerWithdrawalTicket, }, }; use solana_program::{ @@ -39,8 +39,8 @@ pub fn process_burn_withdraw_ticket( staker, mut staker_token_account, staker_lrt_token_account, - vault_staker_withdraw_ticket, - mut vault_staker_withdraw_ticket_token_account, + vault_staker_withdrawal_ticket, + mut vault_staker_withdrawal_ticket_token_account, } = SanitizedAccounts::sanitize(program_id, accounts)?; let slot = Clock::get()?.slot; @@ -51,18 +51,18 @@ pub fn process_burn_withdraw_ticket( ProgramError::InvalidArgument, "LRT mint mismatch", )?; - vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .check_withdrawable(slot, epoch_length)?; // find the current redemption amount and the original redemption amount in the withdraw ticket let redemption_amount = vault.vault().calculate_assets_returned_amount( - vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .lrt_amount(), )?; - let original_redemption_amount = vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + let original_redemption_amount = vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .withdraw_allocation_amount(); let actual_withdraw_amount = if redemption_amount > original_redemption_amount { @@ -105,8 +105,8 @@ pub fn process_burn_withdraw_ticket( .calculate_lrt_mint_amount(actual_withdraw_amount)?; let lrt_amount_to_burn = std::cmp::min( lrt_to_burn, - vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .lrt_amount(), ); @@ -114,13 +114,13 @@ pub fn process_burn_withdraw_ticket( program_id, &vault, &staker, - &vault_staker_withdraw_ticket, - &vault_staker_withdraw_ticket_token_account, + &vault_staker_withdrawal_ticket, + &vault_staker_withdrawal_ticket_token_account, &lrt_mint, lrt_amount_to_burn, )?; lrt_mint.reload()?; - vault_staker_withdraw_ticket_token_account.reload()?; + vault_staker_withdrawal_ticket_token_account.reload()?; _transfer_vault_tokens_to_staker( program_id, @@ -148,15 +148,15 @@ pub fn process_burn_withdraw_ticket( close_program_account( program_id, - vault_staker_withdraw_ticket.account(), + vault_staker_withdrawal_ticket.account(), staker.account(), )?; _close_token_account( program_id, &vault, &staker, - &vault_staker_withdraw_ticket, - &vault_staker_withdraw_ticket_token_account, + &vault_staker_withdrawal_ticket, + &vault_staker_withdrawal_ticket_token_account, &staker_lrt_token_account, )?; @@ -168,8 +168,8 @@ fn _close_token_account<'a, 'info>( program_id: &Pubkey, vault: &SanitizedVault<'a, 'info>, staker: &SanitizedSignerAccount<'a, 'info>, - vault_staker_withdraw_ticket: &SanitizedVaultStakerWithdrawTicket<'a, 'info>, - vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + vault_staker_withdrawal_ticket: &SanitizedVaultStakerWithdrawalTicket<'a, 'info>, + vault_staker_withdrawal_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, staker_lrt_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, ) -> ProgramResult { // TODO: combine with burn lrt method @@ -177,8 +177,8 @@ fn _close_token_account<'a, 'info>( program_id, vault.account().key, staker.account().key, - &vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + &vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .base(), ); seeds.push(vec![bump]); @@ -187,18 +187,20 @@ fn _close_token_account<'a, 'info>( invoke_signed( &transfer( &spl_token::id(), - vault_staker_withdraw_ticket_token_account.account().key, + vault_staker_withdrawal_ticket_token_account.account().key, staker_lrt_token_account.account().key, vault.account().key, &[], - vault_staker_withdraw_ticket_token_account + vault_staker_withdrawal_ticket_token_account .token_account() .amount, )?, &[ - vault_staker_withdraw_ticket_token_account.account().clone(), + vault_staker_withdrawal_ticket_token_account + .account() + .clone(), staker_lrt_token_account.account().clone(), - vault_staker_withdraw_ticket.account().clone(), + vault_staker_withdrawal_ticket.account().clone(), ], &[&seed_slices], )?; @@ -206,13 +208,15 @@ fn _close_token_account<'a, 'info>( invoke_signed( &close_account( &spl_token::id(), - vault_staker_withdraw_ticket_token_account.account().key, + vault_staker_withdrawal_ticket_token_account.account().key, staker.account().key, staker.account().key, &[], )?, &[ - vault_staker_withdraw_ticket_token_account.account().clone(), + vault_staker_withdrawal_ticket_token_account + .account() + .clone(), staker.account().clone(), staker.account().clone(), ], @@ -255,8 +259,8 @@ fn _burn_lrt<'a, 'info>( program_id: &Pubkey, vault: &SanitizedVault<'a, 'info>, staker: &SanitizedSignerAccount<'a, 'info>, - vault_staker_withdraw_ticket: &SanitizedVaultStakerWithdrawTicket<'a, 'info>, - vault_staker_withdraw_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, + vault_staker_withdrawal_ticket: &SanitizedVaultStakerWithdrawalTicket<'a, 'info>, + vault_staker_withdrawal_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, token_mint: &SanitizedTokenMint<'a, 'info>, burn_amount: u64, ) -> ProgramResult { @@ -264,8 +268,8 @@ fn _burn_lrt<'a, 'info>( program_id, vault.account().key, staker.account().key, - &vault_staker_withdraw_ticket - .vault_staker_withdraw_ticket() + &vault_staker_withdrawal_ticket + .vault_staker_withdrawal_ticket() .base(), ); seeds.push(vec![bump]); @@ -274,16 +278,18 @@ fn _burn_lrt<'a, 'info>( invoke_signed( &burn( &spl_token::id(), - vault_staker_withdraw_ticket_token_account.account().key, + vault_staker_withdrawal_ticket_token_account.account().key, token_mint.account().key, vault.account().key, &[], burn_amount, )?, &[ - vault_staker_withdraw_ticket_token_account.account().clone(), + vault_staker_withdrawal_ticket_token_account + .account() + .clone(), token_mint.account().clone(), - vault_staker_withdraw_ticket.account().clone(), + vault_staker_withdrawal_ticket.account().clone(), ], &[&seed_slices], ) @@ -298,8 +304,8 @@ pub struct SanitizedAccounts<'a, 'info> { staker: SanitizedSignerAccount<'a, 'info>, staker_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, staker_lrt_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, - vault_staker_withdraw_ticket: SanitizedVaultStakerWithdrawTicket<'a, 'info>, - vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + vault_staker_withdrawal_ticket: SanitizedVaultStakerWithdrawalTicket<'a, 'info>, + vault_staker_withdrawal_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, } impl<'a, 'info> SanitizedAccounts<'a, 'info> { @@ -335,18 +341,19 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { &vault.vault().lrt_mint(), staker.account().key, )?; - let vault_staker_withdraw_ticket = SanitizedVaultStakerWithdrawTicket::sanitize( + let vault_staker_withdrawal_ticket = SanitizedVaultStakerWithdrawalTicket::sanitize( program_id, next_account_info(accounts_iter)?, vault.account().key, staker.account().key, true, )?; - let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( - next_account_info(accounts_iter)?, - &vault.vault().supported_mint(), - vault_staker_withdraw_ticket.account().key, - )?; + let vault_staker_withdrawal_ticket_token_account = + SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().supported_mint(), + vault_staker_withdrawal_ticket.account().key, + )?; let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; let _system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; @@ -359,8 +366,8 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { staker, staker_token_account, staker_lrt_token_account, - vault_staker_withdraw_ticket, - vault_staker_withdraw_ticket_token_account, + vault_staker_withdrawal_ticket, + vault_staker_withdrawal_ticket_token_account, }) } } diff --git a/vault_program/src/enqueue_withdrawal.rs b/vault_program/src/enqueue_withdrawal.rs index ff360be4..e1fe85d8 100644 --- a/vault_program/src/enqueue_withdrawal.rs +++ b/vault_program/src/enqueue_withdrawal.rs @@ -9,7 +9,7 @@ use jito_vault_core::{ config::SanitizedConfig, vault::SanitizedVault, vault_delegation_list::{SanitizedVaultDelegationList, UndelegateForWithdrawMethod}, - vault_staker_withdraw_ticket::VaultStakerWithdrawalTicket, + vault_staker_withdrawal_ticket::VaultStakerWithdrawalTicket, }; use solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -24,8 +24,8 @@ use solana_program::{ }; use spl_token::instruction::transfer; -/// Enqueues a withdraw into the VaultStakerWithdrawTicket account, transferring the amount from the -/// staker's LRT token account to the VaultStakerWithdrawTicket LRT token account. It also queues +/// Enqueues a withdraw into the VaultStakerWithdrawalTicket account, transferring the amount from the +/// staker's LRT token account to the VaultStakerWithdrawalTicket LRT token account. It also queues /// the withdrawal in the vault's delegation list. /// /// The most obvious options for withdrawing are calculating the redemption ratio and withdrawing @@ -45,8 +45,8 @@ pub fn process_enqueue_withdrawal( config, vault, mut vault_delegation_list, - vault_staker_withdraw_ticket, - vault_staker_withdraw_ticket_token_account, + vault_staker_withdrawal_ticket, + vault_staker_withdrawal_ticket_token_account, vault_fee_token_account, staker, staker_lrt_token_account, @@ -65,7 +65,7 @@ pub fn process_enqueue_withdrawal( // The withdraw fee is subtracted here as opposed to when the withdraw ticket is processed // so the amount representing the fee isn't unstaked. let fee_amount = vault.vault().calculate_withdraw_fee(lrt_amount)?; - let amount_to_vault_staker_withdraw_ticket = lrt_amount + let amount_to_vault_staker_withdrawal_ticket = lrt_amount .checked_sub(fee_amount) .ok_or(ProgramError::ArithmeticOverflow)?; @@ -75,11 +75,11 @@ pub fn process_enqueue_withdrawal( // this point and the redemption. let amount_to_withdraw = vault .vault() - .calculate_assets_returned_amount(amount_to_vault_staker_withdraw_ticket)?; + .calculate_assets_returned_amount(amount_to_vault_staker_withdrawal_ticket)?; msg!( "lrt_supply: {} lrt_amount: {}, amount_to_withdraw: {}", vault.vault().lrt_supply(), - amount_to_vault_staker_withdraw_ticket, + amount_to_vault_staker_withdrawal_ticket, amount_to_withdraw ); @@ -87,25 +87,25 @@ pub fn process_enqueue_withdrawal( .vault_delegation_list_mut() .undelegate_for_withdrawal(amount_to_withdraw, UndelegateForWithdrawMethod::ProRata)?; - _create_vault_staker_withdraw_ticket( + _create_vault_staker_withdrawal_ticket( program_id, &vault, &staker, &base, - &vault_staker_withdraw_ticket, + &vault_staker_withdrawal_ticket, &system_program, &rent, slot, amount_to_withdraw, - amount_to_vault_staker_withdraw_ticket, + amount_to_vault_staker_withdrawal_ticket, )?; - // Transfers the LRT tokens from the staker to their withdraw account and the vault's fee account + // Transfers the LRT tokens from the staker to their withdrawal account and the vault's fee account _transfer_to( &staker_lrt_token_account, - &vault_staker_withdraw_ticket_token_account, + &vault_staker_withdrawal_ticket_token_account, &staker, - amount_to_vault_staker_withdraw_ticket, + amount_to_vault_staker_withdrawal_ticket, )?; _transfer_to( &staker_lrt_token_account, @@ -143,17 +143,17 @@ fn _transfer_to<'a, 'info>( } #[allow(clippy::too_many_arguments)] -fn _create_vault_staker_withdraw_ticket<'a, 'info>( +fn _create_vault_staker_withdrawal_ticket<'a, 'info>( program_id: &Pubkey, vault: &SanitizedVault<'a, 'info>, staker: &SanitizedSignerAccount<'a, 'info>, base: &SanitizedSignerAccount<'a, 'info>, - vault_staker_withdraw_ticket_account: &EmptyAccount<'a, 'info>, + vault_staker_withdrawal_ticket_account: &EmptyAccount<'a, 'info>, system_program: &SanitizedSystemProgram<'a, 'info>, rent: &Rent, slot: Slot, amount_to_withdraw: u64, - amount_to_vault_staker_withdraw_ticket: u64, + amount_to_vault_staker_withdrawal_ticket: u64, ) -> ProgramResult { let (address, bump, mut seeds) = VaultStakerWithdrawalTicket::find_program_address( program_id, @@ -164,36 +164,36 @@ fn _create_vault_staker_withdraw_ticket<'a, 'info>( seeds.push(vec![bump]); assert_with_msg( - address == *vault_staker_withdraw_ticket_account.account().key, + address == *vault_staker_withdrawal_ticket_account.account().key, ProgramError::InvalidAccountData, "Vault staker withdraw ticket is not at the correct PDA", )?; - let vault_staker_withdraw_ticket = VaultStakerWithdrawalTicket::new( + let vault_staker_withdrawal_ticket = VaultStakerWithdrawalTicket::new( *vault.account().key, *staker.account().key, *base.account().key, amount_to_withdraw, - amount_to_vault_staker_withdraw_ticket, + amount_to_vault_staker_withdrawal_ticket, slot, bump, ); msg!( "Creating vault staker withdraw ticket: {:?}", - vault_staker_withdraw_ticket_account.account().key + vault_staker_withdrawal_ticket_account.account().key ); - let serialized = vault_staker_withdraw_ticket.try_to_vec()?; + let serialized = vault_staker_withdrawal_ticket.try_to_vec()?; create_account( staker.account(), - vault_staker_withdraw_ticket_account.account(), + vault_staker_withdrawal_ticket_account.account(), system_program.account(), program_id, rent, serialized.len() as u64, &seeds, )?; - vault_staker_withdraw_ticket_account + vault_staker_withdrawal_ticket_account .account() .data .borrow_mut()[..serialized.len()] @@ -205,8 +205,8 @@ struct SanitizedAccounts<'a, 'info> { config: SanitizedConfig<'a, 'info>, vault: SanitizedVault<'a, 'info>, vault_delegation_list: SanitizedVaultDelegationList<'a, 'info>, - vault_staker_withdraw_ticket: EmptyAccount<'a, 'info>, - vault_staker_withdraw_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, + vault_staker_withdrawal_ticket: EmptyAccount<'a, 'info>, + vault_staker_withdrawal_ticket_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, vault_fee_token_account: SanitizedAssociatedTokenAccount<'a, 'info>, staker: SanitizedSignerAccount<'a, 'info>, staker_lrt_token_account: SanitizedTokenAccount<'a, 'info>, @@ -231,13 +231,14 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { true, vault.account().key, )?; - let vault_staker_withdraw_ticket = + let vault_staker_withdrawal_ticket = EmptyAccount::sanitize(next_account_info(accounts_iter)?, true)?; - let vault_staker_withdraw_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( - next_account_info(accounts_iter)?, - &vault.vault().lrt_mint(), - vault_staker_withdraw_ticket.account().key, - )?; + let vault_staker_withdrawal_ticket_token_account = + SanitizedAssociatedTokenAccount::sanitize( + next_account_info(accounts_iter)?, + &vault.vault().lrt_mint(), + vault_staker_withdrawal_ticket.account().key, + )?; let vault_fee_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), @@ -257,8 +258,8 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { config, vault, vault_delegation_list, - vault_staker_withdraw_ticket, - vault_staker_withdraw_ticket_token_account, + vault_staker_withdrawal_ticket, + vault_staker_withdrawal_ticket_token_account, vault_fee_token_account, staker, staker_lrt_token_account, diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index c1e22860..b95daddd 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -96,8 +96,8 @@ pub enum VaultInstruction { #[account(0, name = "config")] #[account(1, writable, name = "vault")] #[account(2, writable, name = "vault_delegation_list")] - #[account(3, writable, name = "vault_staker_withdraw_ticket")] - #[account(4, writable, name = "vault_staker_withdraw_ticket_token_account")] + #[account(3, writable, name = "vault_staker_withdrawal_ticket")] + #[account(4, writable, name = "vault_staker_withdrawal_ticket_token_account")] #[account(5, writable, name = "vault_fee_token_account")] #[account(6, writable, signer, name = "staker")] #[account(7, writable, name = "staker_lrt_token_account")] @@ -117,8 +117,8 @@ pub enum VaultInstruction { #[account(4, writable, name = "lrt_mint")] #[account(5, writable, signer, name = "staker")] #[account(6, writable, name = "staker_token_account")] - #[account(7, writable, name = "vault_staker_withdraw_ticket")] - #[account(8, writable, name = "vault_staker_withdraw_ticket_token_account")] + #[account(7, writable, name = "vault_staker_withdrawal_ticket")] + #[account(8, writable, name = "vault_staker_withdrawal_ticket_token_account")] #[account(9, name = "token_program")] #[account(10, name = "system_program")] BurnWithdrawTicket, @@ -734,8 +734,8 @@ pub fn enqueue_withdraw( config: &Pubkey, vault: &Pubkey, vault_delegation_list: &Pubkey, - vault_staker_withdraw_ticket: &Pubkey, - vault_staker_withdraw_ticket_token_account: &Pubkey, + vault_staker_withdrawal_ticket: &Pubkey, + vault_staker_withdrawal_ticket_token_account: &Pubkey, vault_fee_token_account: &Pubkey, staker: &Pubkey, staker_lrt_token_account: &Pubkey, @@ -746,8 +746,8 @@ pub fn enqueue_withdraw( AccountMeta::new_readonly(*config, false), AccountMeta::new(*vault, false), AccountMeta::new(*vault_delegation_list, false), - AccountMeta::new(*vault_staker_withdraw_ticket, false), - AccountMeta::new(*vault_staker_withdraw_ticket_token_account, false), + AccountMeta::new(*vault_staker_withdrawal_ticket, false), + AccountMeta::new(*vault_staker_withdrawal_ticket_token_account, false), AccountMeta::new(*vault_fee_token_account, false), AccountMeta::new(*staker, true), AccountMeta::new(*staker_lrt_token_account, false), From 3911f82a655e1f00a16a6e80d260c72efdc85101 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Sun, 4 Aug 2024 20:59:28 -0500 Subject: [PATCH 15/22] rename --- .../tests/vault/burn_withdrawal_ticket.rs | 0 .../tests/vault/enqueue_withdrawal.rs | 39 ++----------------- integration_tests/tests/vault/mod.rs | 1 + ...aw_ticket.rs => burn_withdrawal_ticket.rs} | 9 ++--- vault_program/src/lib.rs | 6 +-- 5 files changed, 11 insertions(+), 44 deletions(-) create mode 100644 integration_tests/tests/vault/burn_withdrawal_ticket.rs rename vault_program/src/{burn_withdraw_ticket.rs => burn_withdrawal_ticket.rs} (97%) diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs new file mode 100644 index 00000000..e69de29b diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index 498d533b..62d2919b 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -291,15 +291,15 @@ async fn test_enqueue_withdraw_with_reward_ok() { .unwrap(); // Verify the withdraw ticket - let withdraw_ticket = vault_program_client + let withdrawal_ticket = vault_program_client .get_vault_staker_withdrawal_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) .await .unwrap(); - assert_eq!(withdraw_ticket.lrt_amount(), withdraw_amount); + assert_eq!(withdrawal_ticket.lrt_amount(), withdraw_amount); // The actual assets to be withdrawn should be more than the LRT amount due to rewards - assert_eq!(withdraw_ticket.withdraw_allocation_amount(), 55_000); + assert_eq!(withdrawal_ticket.withdraw_allocation_amount(), 55_000); // Verify the vault delegation list let vault_delegation_list = vault_program_client @@ -312,36 +312,3 @@ async fn test_enqueue_withdraw_with_reward_ok() { assert_eq!(delegation.enqueued_for_withdraw_amount(), 55_000); assert_eq!(delegation.total_security().unwrap(), 100_000); } - -#[tokio::test] -async fn test_enqueue_withdraw_with_slash_ok() {} - -#[tokio::test] -async fn test_enqueue_withdraw_with_multiple_operators_pro_rata_ok() {} - -#[tokio::test] -async fn test_enqueue_withdraw_at_epoch_boundary() {} - -#[tokio::test] -async fn test_enqueue_withdraw_with_existing_cooldowns() {} - -#[tokio::test] -async fn test_enqueue_withdraw_with_zero_amount() {} - -#[tokio::test] -async fn test_enqueue_withdraw_insufficient_balance() {} - -#[tokio::test] -async fn test_enqueue_withdraw_concurrent_requests() {} - -#[tokio::test] -async fn test_enqueue_multiple_same_ticket() {} - -#[tokio::test] -async fn test_enqueue_delegation_list_update_needed() {} - -#[tokio::test] -async fn test_enqueue_withdraw_with_all_assets_cooling_down() {} - -#[tokio::test] -async fn test_enqueue_withdraw_with_partially_cooling_down_assets() {} diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index 93af001b..97c7e409 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -1,6 +1,7 @@ mod add_avs; mod add_operator; mod add_slasher; +mod burn_withdrawal_ticket; mod enqueue_withdrawal; mod initialize_config; mod initialize_vault; diff --git a/vault_program/src/burn_withdraw_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs similarity index 97% rename from vault_program/src/burn_withdraw_ticket.rs rename to vault_program/src/burn_withdrawal_ticket.rs index 4404656b..adaf402c 100644 --- a/vault_program/src/burn_withdraw_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -22,11 +22,11 @@ use solana_program::{ }; use spl_token::instruction::{burn, close_account, transfer}; -/// Burns the withdraw ticket, transferring the assets to the staker and closing the withdraw ticket. +/// Burns the withdrawal ticket, transferring the assets to the staker and closing the withdraw ticket. /// /// One should call the [`crate::VaultInstruction::UpdateVault`] instruction before running this instruction /// to ensure that any rewards that were accrued are accounted for. -pub fn process_burn_withdraw_ticket( +pub fn process_burn_withdrawal_ticket( program_id: &Pubkey, accounts: &[AccountInfo], ) -> ProgramResult { @@ -77,14 +77,14 @@ pub fn process_burn_withdraw_ticket( let delegated_security_in_vault = vault_delegation_list .vault_delegation_list() .all_security()?; - let assets_reserved_for_withdraw_tickets = vault_delegation_list + let assets_reserved_for_withdrawal_tickets = vault_delegation_list .vault_delegation_list() .withdrawable_reserve_amount(); let available_unstaked_assets = tokens_deposited_in_vault .checked_sub(delegated_security_in_vault) .ok_or(ProgramError::InsufficientFunds)? - .checked_sub(assets_reserved_for_withdraw_tickets) + .checked_sub(assets_reserved_for_withdrawal_tickets) .ok_or(ProgramError::InsufficientFunds)?; // Calculate the extra amount that can be withdrawn @@ -172,7 +172,6 @@ fn _close_token_account<'a, 'info>( vault_staker_withdrawal_ticket_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, staker_lrt_token_account: &SanitizedAssociatedTokenAccount<'a, 'info>, ) -> ProgramResult { - // TODO: combine with burn lrt method let (_, bump, mut seeds) = VaultStakerWithdrawalTicket::find_program_address( program_id, vault.account().key, diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index 58644ccb..237ebb84 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -3,7 +3,7 @@ mod add_delegation; mod add_operator; mod add_slasher; mod burn; -mod burn_withdraw_ticket; +mod burn_withdrawal_ticket; mod create_token_metadata; mod enqueue_withdrawal; mod initialize_config; @@ -34,7 +34,7 @@ use solana_security_txt::security_txt; use crate::{ add_avs::process_vault_add_avs, add_delegation::process_add_delegation, add_operator::process_vault_add_operator, add_slasher::process_add_slasher, burn::process_burn, - burn_withdraw_ticket::process_burn_withdraw_ticket, + burn_withdrawal_ticket::process_burn_withdrawal_ticket, create_token_metadata::process_create_token_metadata, enqueue_withdrawal::process_enqueue_withdrawal, initialize_config::process_initialize_config, initialize_vault::process_initialize_vault, @@ -130,7 +130,7 @@ pub fn process_instruction( } VaultInstruction::BurnWithdrawTicket => { msg!("Instruction: BurnWithdrawTicket"); - process_burn_withdraw_ticket(program_id, accounts) + process_burn_withdrawal_ticket(program_id, accounts) } // ------------------------------------------ // Vault-AVS operations From 055f8bebff6e66c7f9d8a4b0ef501ca9c467d5ff Mon Sep 17 00:00:00 2001 From: Lucas B Date: Wed, 7 Aug 2024 14:42:28 -0400 Subject: [PATCH 16/22] werd --- integration_tests/tests/fixtures/fixture.rs | 20 +- .../tests/fixtures/vault_client.rs | 76 ++ .../tests/restaking/avs_add_operator.rs | 701 ++++++------- .../tests/restaking/avs_add_vault.rs | 120 +-- .../tests/restaking/avs_add_vault_slasher.rs | 172 ++-- .../tests/restaking/initialize_avs.rs | 404 ++++---- .../tests/restaking/initialize_config.rs | 151 +-- .../tests/restaking/initialize_operator.rs | 476 ++++----- .../tests/restaking/operator_add_avs.rs | 920 +++++++++--------- .../tests/restaking/operator_add_vault.rs | 156 +-- integration_tests/tests/vault/add_avs.rs | 69 +- integration_tests/tests/vault/add_operator.rs | 75 +- integration_tests/tests/vault/add_slasher.rs | 107 +- .../tests/vault/burn_withdrawal_ticket.rs | 260 +++++ .../tests/vault/enqueue_withdrawal.rs | 638 ++++++------ .../tests/vault/initialize_config.rs | 35 +- .../tests/vault/initialize_vault.rs | 63 +- integration_tests/tests/vault/slash.rs | 371 +++---- vault_core/src/lib.rs | 1 - .../src/vault_staker_withdrawal_ticket.rs | 19 +- vault_program/src/burn_withdrawal_ticket.rs | 63 +- vault_program/src/slash.rs | 18 - vault_sdk/src/lib.rs | 44 +- 23 files changed, 2705 insertions(+), 2254 deletions(-) diff --git a/integration_tests/tests/fixtures/fixture.rs b/integration_tests/tests/fixtures/fixture.rs index 4953a111..0a561a7b 100644 --- a/integration_tests/tests/fixtures/fixture.rs +++ b/integration_tests/tests/fixtures/fixture.rs @@ -1,7 +1,8 @@ use std::fmt::{Debug, Formatter}; use solana_program::{ - native_token::sol_to_lamports, program_pack::Pack, pubkey::Pubkey, system_instruction::transfer, + clock::Clock, native_token::sol_to_lamports, program_pack::Pack, pubkey::Pubkey, + system_instruction::transfer, }; use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; use solana_sdk::{commitment_config::CommitmentLevel, signature::Signer, transaction::Transaction}; @@ -136,13 +137,16 @@ impl TestBuilder { .await } - // pub async fn warp_to_next_slot(&mut self) -> Result<(), BanksClientError> { - // let clock: Clock = self.context.banks_client.get_sysvar().await?; - // self.context - // .warp_to_slot(clock.slot.checked_add(1).unwrap()) - // .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; - // Ok(()) - // } + pub async fn warp_slot_incremental( + &mut self, + incremental_slots: u64, + ) -> Result<(), BanksClientError> { + let clock: Clock = self.context.banks_client.get_sysvar().await?; + self.context + .warp_to_slot(clock.slot.checked_add(incremental_slots).unwrap()) + .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; + Ok(()) + } pub fn vault_program_client(&self) -> VaultProgramClient { VaultProgramClient::new( diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index e8618751..18a3b98a 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -11,6 +11,7 @@ use jito_vault_core::{ vault_staker_withdrawal_ticket::VaultStakerWithdrawalTicket, }; use jito_vault_sdk::{add_delegation, initialize_config, initialize_vault}; +use log::info; use solana_program::{ native_token::sol_to_lamports, program_pack::Pack, @@ -625,6 +626,10 @@ impl VaultProgramClient { &base.pubkey(), ) .0; + info!( + "vault_staker_withdrawal_ticket: {:?}", + vault_staker_withdrawal_ticket + ); let vault_staker_withdrawal_ticket_token_account = get_associated_token_address(&vault_staker_withdrawal_ticket, &vault.lrt_mint()); @@ -713,6 +718,77 @@ impl VaultProgramClient { .await } + pub async fn do_burn_withdrawal_ticket( + &mut self, + vault_root: &VaultRoot, + staker: &Keypair, + vault_staker_withdrawal_ticket_base: &Pubkey, + ) -> Result<(), BanksClientError> { + let vault = self.get_vault(&vault_root.vault_pubkey).await.unwrap(); + let vault_staker_withdrawal_ticket = VaultStakerWithdrawalTicket::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + &staker.pubkey(), + vault_staker_withdrawal_ticket_base, + ) + .0; + + self.burn_withdrawal_ticket( + &Config::find_program_address(&jito_vault_program::id()).0, + &vault_root.vault_pubkey, + &VaultDelegationList::find_program_address( + &jito_vault_program::id(), + &vault_root.vault_pubkey, + ) + .0, + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &vault.lrt_mint(), + staker, + &get_associated_token_address(&staker.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&staker.pubkey(), &vault.lrt_mint()), + &vault_staker_withdrawal_ticket, + &get_associated_token_address(&vault_staker_withdrawal_ticket, &vault.lrt_mint()), + ) + .await?; + + Ok(()) + } + + pub async fn burn_withdrawal_ticket( + &mut self, + config: &Pubkey, + vault: &Pubkey, + vault_delegation_list: &Pubkey, + vault_token_account: &Pubkey, + lrt_mint: &Pubkey, + staker: &Keypair, + staker_token_account: &Pubkey, + staker_lrt_token_account: &Pubkey, + vault_staker_withdrawal_ticket: &Pubkey, + vault_staker_withdrawal_ticket_token_account: &Pubkey, + ) -> Result<(), BanksClientError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::burn_withdrawal_ticket( + &jito_vault_program::id(), + config, + vault, + vault_delegation_list, + vault_token_account, + lrt_mint, + &staker.pubkey(), + staker_token_account, + staker_lrt_token_account, + vault_staker_withdrawal_ticket, + vault_staker_withdrawal_ticket_token_account, + )], + Some(&staker.pubkey()), + &[staker], + blockhash, + )) + .await + } + pub async fn add_delegation( &mut self, config: &Pubkey, diff --git a/integration_tests/tests/restaking/avs_add_operator.rs b/integration_tests/tests/restaking/avs_add_operator.rs index 363ce419..9aec54e5 100644 --- a/integration_tests/tests/restaking/avs_add_operator.rs +++ b/integration_tests/tests/restaking/avs_add_operator.rs @@ -1,367 +1,378 @@ -use jito_restaking_core::{ - avs::Avs, avs_operator_ticket::AvsOperatorTicket, config::Config, operator::Operator, - operator_avs_ticket::OperatorAvsTicket, -}; -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_avs_add_operator_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Operator adds AVS - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - restaking_program_client - .operator_add_avs( - &config, +#[cfg(test)] +mod tests { + use jito_restaking_core::{ + avs::Avs, avs_operator_ticket::AvsOperatorTicket, config::Config, operator::Operator, + operator_avs_ticket::OperatorAvsTicket, + }; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_avs_add_operator_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Operator adds AVS + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // AVS adds operator - let avs_operator_ticket = AvsOperatorTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &operator_pubkey, - ) - .0; - restaking_program_client - .avs_add_operator( - &config, + .0; + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // AVS adds operator + let avs_operator_ticket = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &operator_pubkey, - &avs_operator_ticket, - &operator_avs_ticket, - &avs_admin, - &payer, ) - .await - .unwrap(); - - // Verify AVS state - let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); - assert_eq!(avs.operator_count(), 1); - - // Verify AVS operator ticket - let ticket = restaking_program_client - .get_avs_operator_ticket(&avs_pubkey, &operator_pubkey) - .await - .unwrap(); - assert_eq!(ticket.avs(), avs_pubkey); - assert_eq!(ticket.operator(), operator_pubkey); - assert_eq!(ticket.index(), 0); - assert_eq!(ticket.state().slot_added(), 1); -} - -#[tokio::test] -async fn test_avs_add_operator_without_operator_opt_in_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Attempt to add operator without operator opting in first - let avs_operator_ticket = AvsOperatorTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &operator_pubkey, - ) - .0; - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - let result = restaking_program_client - .avs_add_operator( - &config, + .0; + restaking_program_client + .avs_add_operator( + &config, + &avs_pubkey, + &operator_pubkey, + &avs_operator_ticket, + &operator_avs_ticket, + &avs_admin, + &payer, + ) + .await + .unwrap(); + + // Verify AVS state + let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); + assert_eq!(avs.operator_count(), 1); + + // Verify AVS operator ticket + let ticket = restaking_program_client + .get_avs_operator_ticket(&avs_pubkey, &operator_pubkey) + .await + .unwrap(); + assert_eq!(ticket.avs(), avs_pubkey); + assert_eq!(ticket.operator(), operator_pubkey); + assert_eq!(ticket.index(), 0); + assert_eq!(ticket.state().slot_added(), 1); + } + + #[tokio::test] + async fn test_avs_add_operator_without_operator_opt_in_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Attempt to add operator without operator opting in first + let avs_operator_ticket = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &operator_pubkey, - &avs_operator_ticket, - &operator_avs_ticket, - &avs_admin, - &payer, ) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_avs_add_operator_non_admin_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Operator adds AVS - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - restaking_program_client - .operator_add_avs( - &config, + .0; + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, - ) - .await - .unwrap(); - - // Attempt to add operator with non-admin signer - let non_admin = Keypair::new(); - fixture.transfer(&non_admin.pubkey(), 10.0).await.unwrap(); - - let avs_operator_ticket = AvsOperatorTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &operator_pubkey, - ) - .0; - let result = restaking_program_client - .avs_add_operator( - &config, - &avs_pubkey, - &operator_pubkey, - &avs_operator_ticket, - &operator_avs_ticket, - &non_admin, - &payer, ) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_avs_add_operator_duplicate_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Operator adds AVS - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - restaking_program_client - .operator_add_avs( - &config, + .0; + let result = restaking_program_client + .avs_add_operator( + &config, + &avs_pubkey, + &operator_pubkey, + &avs_operator_ticket, + &operator_avs_ticket, + &avs_admin, + &payer, + ) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_avs_add_operator_non_admin_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Operator adds AVS + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // AVS adds operator (first time) - let avs_operator_ticket = AvsOperatorTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &operator_pubkey, - ) - .0; - restaking_program_client - .avs_add_operator( - &config, + .0; + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // Attempt to add operator with non-admin signer + let non_admin = Keypair::new(); + fixture.transfer(&non_admin.pubkey(), 10.0).await.unwrap(); + + let avs_operator_ticket = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &operator_pubkey, - &avs_operator_ticket, - &operator_avs_ticket, - &avs_admin, - &payer, ) - .await - .unwrap(); - - // Attempt to add the same operator again - let result = restaking_program_client - .avs_add_operator( - &config, + .0; + let result = restaking_program_client + .avs_add_operator( + &config, + &avs_pubkey, + &operator_pubkey, + &avs_operator_ticket, + &operator_avs_ticket, + &non_admin, + &payer, + ) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_avs_add_operator_duplicate_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Operator adds AVS + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), + &operator_pubkey, + &avs_pubkey, + ) + .0; + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // AVS adds operator (first time) + let avs_operator_ticket = AvsOperatorTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &operator_pubkey, - &avs_operator_ticket, - &operator_avs_ticket, - &avs_admin, - &payer, ) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); + .0; + restaking_program_client + .avs_add_operator( + &config, + &avs_pubkey, + &operator_pubkey, + &avs_operator_ticket, + &operator_avs_ticket, + &avs_admin, + &payer, + ) + .await + .unwrap(); + + // Attempt to add the same operator again + let result = restaking_program_client + .avs_add_operator( + &config, + &avs_pubkey, + &operator_pubkey, + &avs_operator_ticket, + &operator_avs_ticket, + &avs_admin, + &payer, + ) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } } diff --git a/integration_tests/tests/restaking/avs_add_vault.rs b/integration_tests/tests/restaking/avs_add_vault.rs index 1e87ab21..21d12c75 100644 --- a/integration_tests/tests/restaking/avs_add_vault.rs +++ b/integration_tests/tests/restaking/avs_add_vault.rs @@ -1,70 +1,74 @@ -use jito_restaking_core::{avs::Avs, avs_vault_ticket::AvsVaultTicket, config::Config}; -use jito_vault_core::vault::Vault; -use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; +#[cfg(test)] +mod tests { + use jito_restaking_core::{avs::Avs, avs_vault_ticket::AvsVaultTicket, config::Config}; + use jito_vault_core::vault::Vault; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; -use crate::fixtures::fixture::TestBuilder; + use crate::fixtures::fixture::TestBuilder; -#[tokio::test] -async fn test_avs_add_vault_happy_path() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); + #[tokio::test] + async fn test_avs_add_vault_happy_path() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); - let vault_pubkey = - Vault::find_program_address(&jito_restaking_program::id(), &Pubkey::new_unique()).0; + let vault_pubkey = + Vault::find_program_address(&jito_restaking_program::id(), &Pubkey::new_unique()).0; - // AVS adds vault - let avs_vault_ticket = AvsVaultTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - ) - .0; - restaking_program_client - .avs_add_vault( - &config, + // AVS adds vault + let avs_vault_ticket = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &vault_pubkey, - &avs_vault_ticket, - &avs_admin, - &avs_admin, ) - .await - .unwrap(); + .0; + restaking_program_client + .avs_add_vault( + &config, + &avs_pubkey, + &vault_pubkey, + &avs_vault_ticket, + &avs_admin, + &avs_admin, + ) + .await + .unwrap(); - // Verify AVS state - let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); - assert_eq!(avs.vault_count(), 1); + // Verify AVS state + let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); + assert_eq!(avs.vault_count(), 1); - // Verify AVS vault ticket - let ticket = restaking_program_client - .get_avs_vault_ticket(&avs_pubkey, &vault_pubkey) - .await - .unwrap(); - assert_eq!(ticket.avs(), avs_pubkey); - assert_eq!(ticket.vault(), vault_pubkey); - assert_eq!(ticket.index(), 0); - assert_eq!(ticket.state().slot_added(), 1); + // Verify AVS vault ticket + let ticket = restaking_program_client + .get_avs_vault_ticket(&avs_pubkey, &vault_pubkey) + .await + .unwrap(); + assert_eq!(ticket.avs(), avs_pubkey); + assert_eq!(ticket.vault(), vault_pubkey); + assert_eq!(ticket.index(), 0); + assert_eq!(ticket.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/restaking/avs_add_vault_slasher.rs b/integration_tests/tests/restaking/avs_add_vault_slasher.rs index 0a15b353..77de7ffa 100644 --- a/integration_tests/tests/restaking/avs_add_vault_slasher.rs +++ b/integration_tests/tests/restaking/avs_add_vault_slasher.rs @@ -1,101 +1,105 @@ -use jito_restaking_core::{ - avs::Avs, avs_vault_slasher_ticket::AvsVaultSlasherTicket, avs_vault_ticket::AvsVaultTicket, - config::Config, -}; -use jito_vault_core::vault::Vault; -use solana_sdk::signature::{Keypair, Signer}; +#[cfg(test)] +mod tests { + use jito_restaking_core::{ + avs::Avs, avs_vault_slasher_ticket::AvsVaultSlasherTicket, + avs_vault_ticket::AvsVaultTicket, config::Config, + }; + use jito_vault_core::vault::Vault; + use solana_sdk::signature::{Keypair, Signer}; -use crate::fixtures::fixture::TestBuilder; + use crate::fixtures::fixture::TestBuilder; -#[tokio::test] -async fn test_avs_add_vault_slasher_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); + #[tokio::test] + async fn test_avs_add_vault_slasher_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); - // Initialize Vault - let vault_base = Keypair::new(); - let vault_pubkey = - Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; + // Initialize Vault + let vault_base = Keypair::new(); + let vault_pubkey = + Vault::find_program_address(&jito_vault_program::id(), &vault_base.pubkey()).0; - // AVS adds vault - let avs_vault_ticket = AvsVaultTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - ) - .0; - restaking_program_client - .avs_add_vault( - &config, + // AVS adds vault + let avs_vault_ticket = AvsVaultTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &vault_pubkey, - &avs_vault_ticket, - &avs_admin, - &avs_admin, ) - .await - .unwrap(); + .0; + restaking_program_client + .avs_add_vault( + &config, + &avs_pubkey, + &vault_pubkey, + &avs_vault_ticket, + &avs_admin, + &avs_admin, + ) + .await + .unwrap(); - // AVS adds vault slasher - let slasher = Keypair::new(); - let avs_vault_slasher_ticket = AvsVaultSlasherTicket::find_program_address( - &jito_restaking_program::id(), - &avs_pubkey, - &vault_pubkey, - &slasher.pubkey(), - ) - .0; - let max_slashable_per_epoch = 1000; - restaking_program_client - .avs_add_vault_slasher( - &config, + // AVS adds vault slasher + let slasher = Keypair::new(); + let avs_vault_slasher_ticket = AvsVaultSlasherTicket::find_program_address( + &jito_restaking_program::id(), &avs_pubkey, &vault_pubkey, &slasher.pubkey(), - &avs_vault_ticket, - &avs_vault_slasher_ticket, - &avs_admin, - &avs_admin, - max_slashable_per_epoch, ) - .await - .unwrap(); + .0; + let max_slashable_per_epoch = 1000; + restaking_program_client + .avs_add_vault_slasher( + &config, + &avs_pubkey, + &vault_pubkey, + &slasher.pubkey(), + &avs_vault_ticket, + &avs_vault_slasher_ticket, + &avs_admin, + &avs_admin, + max_slashable_per_epoch, + ) + .await + .unwrap(); - // Verify AVS state - let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); - assert_eq!(avs.slasher_count(), 1); + // Verify AVS state + let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); + assert_eq!(avs.slasher_count(), 1); - // Verify AVS vault slasher ticket - let ticket = restaking_program_client - .get_avs_vault_slasher_ticket(&avs_pubkey, &vault_pubkey, &slasher.pubkey()) - .await - .unwrap(); - assert_eq!(ticket.avs(), avs_pubkey); - assert_eq!(ticket.vault(), vault_pubkey); - assert_eq!(ticket.slasher(), slasher.pubkey()); - assert_eq!(ticket.max_slashable_per_epoch(), max_slashable_per_epoch); - assert_eq!(ticket.index(), 0); - assert_eq!(ticket.state().slot_added(), 1); + // Verify AVS vault slasher ticket + let ticket = restaking_program_client + .get_avs_vault_slasher_ticket(&avs_pubkey, &vault_pubkey, &slasher.pubkey()) + .await + .unwrap(); + assert_eq!(ticket.avs(), avs_pubkey); + assert_eq!(ticket.vault(), vault_pubkey); + assert_eq!(ticket.slasher(), slasher.pubkey()); + assert_eq!(ticket.max_slashable_per_epoch(), max_slashable_per_epoch); + assert_eq!(ticket.index(), 0); + assert_eq!(ticket.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/restaking/initialize_avs.rs b/integration_tests/tests/restaking/initialize_avs.rs index b9ef3385..a621f7d7 100644 --- a/integration_tests/tests/restaking/initialize_avs.rs +++ b/integration_tests/tests/restaking/initialize_avs.rs @@ -1,200 +1,206 @@ -use jito_restaking_core::{avs::Avs, config::Config}; -use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_initialize_avs_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config first - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - // Verify AVS account - let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); - assert_eq!(avs.base(), avs_base.pubkey()); - assert_eq!(avs.admin(), avs_admin.pubkey()); - assert_eq!(avs.operator_admin(), avs_admin.pubkey()); - assert_eq!(avs.vault_admin(), avs_admin.pubkey()); - assert_eq!(avs.slasher_admin(), avs_admin.pubkey()); - assert_eq!(avs.withdraw_admin(), avs_admin.pubkey()); - assert_eq!(avs.index(), 0); - assert_eq!(avs.operator_count(), 0); - assert_eq!(avs.slasher_count(), 0); - assert_eq!(avs.vault_count(), 0); - - let updated_config = restaking_program_client.get_config(&config).await.unwrap(); - assert_eq!(updated_config.avs_count(), 1); -} - -#[tokio::test] -async fn test_initialize_avs_bad_derivation_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Try to initialize AVS with incorrect derivation - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - - let incorrect_avs_pubkey = Pubkey::new_unique(); // This is not derived correctly - - let result = restaking_program_client - .initialize_avs(&config, &incorrect_avs_pubkey, &avs_admin, &avs_base) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_avs_already_initialized_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let result = restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_avs_with_uninitialized_config_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Try to initialize AVS without initializing config first - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - - let config = Config::find_program_address(&jito_restaking_program::id()).0; - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - - let result = restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_multiple_avs_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize first AVS - let avs_admin1 = Keypair::new(); - let avs_base1 = Keypair::new(); - fixture.transfer(&avs_admin1.pubkey(), 10.0).await.unwrap(); - let avs_pubkey1 = - Avs::find_program_address(&jito_restaking_program::id(), &avs_base1.pubkey()).0; - - restaking_program_client - .initialize_avs(&config, &avs_pubkey1, &avs_admin1, &avs_base1) - .await - .unwrap(); - - // Initialize second AVS - let avs_admin2 = Keypair::new(); - let avs_base2 = Keypair::new(); - fixture.transfer(&avs_admin2.pubkey(), 10.0).await.unwrap(); - let avs_pubkey2 = - Avs::find_program_address(&jito_restaking_program::id(), &avs_base2.pubkey()).0; - - restaking_program_client - .initialize_avs(&config, &avs_pubkey2, &avs_admin2, &avs_base2) - .await - .unwrap(); - - // Verify AVS accounts - let avs1 = restaking_program_client - .get_avs(&avs_pubkey1) - .await - .unwrap(); - let avs2 = restaking_program_client - .get_avs(&avs_pubkey2) - .await - .unwrap(); - - assert_eq!(avs1.index(), 0); - assert_eq!(avs2.index(), 1); - - // Verify config update - let updated_config = restaking_program_client.get_config(&config).await.unwrap(); - assert_eq!(updated_config.avs_count(), 2); +#[cfg(test)] +mod tests { + use jito_restaking_core::{avs::Avs, config::Config}; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_initialize_avs_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config first + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + // Verify AVS account + let avs = restaking_program_client.get_avs(&avs_pubkey).await.unwrap(); + assert_eq!(avs.base(), avs_base.pubkey()); + assert_eq!(avs.admin(), avs_admin.pubkey()); + assert_eq!(avs.operator_admin(), avs_admin.pubkey()); + assert_eq!(avs.vault_admin(), avs_admin.pubkey()); + assert_eq!(avs.slasher_admin(), avs_admin.pubkey()); + assert_eq!(avs.withdraw_admin(), avs_admin.pubkey()); + assert_eq!(avs.index(), 0); + assert_eq!(avs.operator_count(), 0); + assert_eq!(avs.slasher_count(), 0); + assert_eq!(avs.vault_count(), 0); + + let updated_config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(updated_config.avs_count(), 1); + } + + #[tokio::test] + async fn test_initialize_avs_bad_derivation_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Try to initialize AVS with incorrect derivation + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + + let incorrect_avs_pubkey = Pubkey::new_unique(); // This is not derived correctly + + let result = restaking_program_client + .initialize_avs(&config, &incorrect_avs_pubkey, &avs_admin, &avs_base) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_avs_already_initialized_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let result = restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_avs_with_uninitialized_config_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Try to initialize AVS without initializing config first + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + + let config = Config::find_program_address(&jito_restaking_program::id()).0; + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + + let result = restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_multiple_avs_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize first AVS + let avs_admin1 = Keypair::new(); + let avs_base1 = Keypair::new(); + fixture.transfer(&avs_admin1.pubkey(), 10.0).await.unwrap(); + let avs_pubkey1 = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base1.pubkey()).0; + + restaking_program_client + .initialize_avs(&config, &avs_pubkey1, &avs_admin1, &avs_base1) + .await + .unwrap(); + + // Initialize second AVS + let avs_admin2 = Keypair::new(); + let avs_base2 = Keypair::new(); + fixture.transfer(&avs_admin2.pubkey(), 10.0).await.unwrap(); + let avs_pubkey2 = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base2.pubkey()).0; + + restaking_program_client + .initialize_avs(&config, &avs_pubkey2, &avs_admin2, &avs_base2) + .await + .unwrap(); + + // Verify AVS accounts + let avs1 = restaking_program_client + .get_avs(&avs_pubkey1) + .await + .unwrap(); + let avs2 = restaking_program_client + .get_avs(&avs_pubkey2) + .await + .unwrap(); + + assert_eq!(avs1.index(), 0); + assert_eq!(avs2.index(), 1); + + // Verify config update + let updated_config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(updated_config.avs_count(), 2); + } } diff --git a/integration_tests/tests/restaking/initialize_config.rs b/integration_tests/tests/restaking/initialize_config.rs index e826b568..0e25f4fa 100644 --- a/integration_tests/tests/restaking/initialize_config.rs +++ b/integration_tests/tests/restaking/initialize_config.rs @@ -1,75 +1,78 @@ -use jito_restaking_core::config::Config; -use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_initialize_config_ok() { - let mut fixture = TestBuilder::new().await; - - let mut restaking_program_client = fixture.restaking_program_client(); - - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - let config = restaking_program_client.get_config(&config).await.unwrap(); - assert_eq!(config.admin(), config_admin.pubkey()); - assert_eq!(config.vault_program(), jito_vault_program::id()); - assert_eq!(config.avs_count(), 0); - assert_eq!(config.operators_count(), 0); -} - -#[tokio::test] -async fn test_initialize_config_double_init_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - let result = restaking_program_client - .initialize_config(&config, &config_admin) - .await; - - // TODO (LB): check specific error here - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_config_bad_pda_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - let config_admin = Keypair::new(); - - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - - let result = restaking_program_client - .initialize_config(&Pubkey::new_unique(), &config_admin) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); +#[cfg(test)] +mod tests { + use jito_restaking_core::config::Config; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_initialize_config_ok() { + let mut fixture = TestBuilder::new().await; + + let mut restaking_program_client = fixture.restaking_program_client(); + + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + let config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(config.admin(), config_admin.pubkey()); + assert_eq!(config.vault_program(), jito_vault_program::id()); + assert_eq!(config.avs_count(), 0); + assert_eq!(config.operators_count(), 0); + } + + #[tokio::test] + async fn test_initialize_config_double_init_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + let result = restaking_program_client + .initialize_config(&config, &config_admin) + .await; + + // TODO (LB): check specific error here + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_config_bad_pda_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + let config_admin = Keypair::new(); + + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + + let result = restaking_program_client + .initialize_config(&Pubkey::new_unique(), &config_admin) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } } diff --git a/integration_tests/tests/restaking/initialize_operator.rs b/integration_tests/tests/restaking/initialize_operator.rs index 31fea623..4528b056 100644 --- a/integration_tests/tests/restaking/initialize_operator.rs +++ b/integration_tests/tests/restaking/initialize_operator.rs @@ -1,235 +1,243 @@ -use jito_restaking_core::{config::Config, operator::Operator}; -use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_initialize_operator_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config first - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize Operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - let operator = restaking_program_client - .get_operator(&operator_pubkey) - .await - .unwrap(); - assert_eq!(operator.base(), operator_base.pubkey()); - assert_eq!(operator.admin(), operator_admin.pubkey()); - assert_eq!(operator.voter(), operator_admin.pubkey()); - assert_eq!(operator.avs_admin(), operator_admin.pubkey()); - assert_eq!(operator.vault_admin(), operator_admin.pubkey()); - assert_eq!(operator.index(), 0); - - let updated_config = restaking_program_client.get_config(&config).await.unwrap(); - assert_eq!(updated_config.operators_count(), 1); -} - -#[tokio::test] -async fn test_initialize_operator_bad_derivation_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Try to initialize Operator with incorrect derivation - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - - let incorrect_operator_pubkey = Pubkey::new_unique(); // This is not derived correctly - - let result = restaking_program_client - .initialize_operator( - &config, - &incorrect_operator_pubkey, - &operator_admin, - &operator_base, - ) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_operator_already_initialized_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize Operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Try to initialize the same Operator again - let result = restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_operator_with_uninitialized_config_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Try to initialize Operator without initializing config first - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - - let config = Config::find_program_address(&jito_restaking_program::id()).0; - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - - let result = restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await; - - // TODO (LB): check specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_initialize_multiple_operators_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize first operator - let operator_admin1 = Keypair::new(); - let operator_base1 = Keypair::new(); - fixture - .transfer(&operator_admin1.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey1 = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base1.pubkey()).0; - - restaking_program_client - .initialize_operator( - &config, - &operator_pubkey1, - &operator_admin1, - &operator_base1, - ) - .await - .unwrap(); - - // Initialize second operator - let operator_admin2 = Keypair::new(); - let operator_base2 = Keypair::new(); - fixture - .transfer(&operator_admin2.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey2 = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base2.pubkey()).0; - - restaking_program_client - .initialize_operator( - &config, - &operator_pubkey2, - &operator_admin2, - &operator_base2, - ) - .await - .unwrap(); - - // Verify operator accounts - let operator1 = restaking_program_client - .get_operator(&operator_pubkey1) - .await - .unwrap(); - let operator2 = restaking_program_client - .get_operator(&operator_pubkey2) - .await - .unwrap(); - - assert_eq!(operator1.index(), 0); - assert_eq!(operator2.index(), 1); - - // Verify config update - let updated_config = restaking_program_client.get_config(&config).await.unwrap(); - assert_eq!(updated_config.operators_count(), 2); +#[cfg(test)] +mod tests { + use jito_restaking_core::{config::Config, operator::Operator}; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_initialize_operator_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config first + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize Operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + let operator = restaking_program_client + .get_operator(&operator_pubkey) + .await + .unwrap(); + assert_eq!(operator.base(), operator_base.pubkey()); + assert_eq!(operator.admin(), operator_admin.pubkey()); + assert_eq!(operator.voter(), operator_admin.pubkey()); + assert_eq!(operator.avs_admin(), operator_admin.pubkey()); + assert_eq!(operator.vault_admin(), operator_admin.pubkey()); + assert_eq!(operator.index(), 0); + + let updated_config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(updated_config.operators_count(), 1); + } + + #[tokio::test] + async fn test_initialize_operator_bad_derivation_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Try to initialize Operator with incorrect derivation + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + + let incorrect_operator_pubkey = Pubkey::new_unique(); // This is not derived correctly + + let result = restaking_program_client + .initialize_operator( + &config, + &incorrect_operator_pubkey, + &operator_admin, + &operator_base, + ) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_operator_already_initialized_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize Operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Try to initialize the same Operator again + let result = restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_operator_with_uninitialized_config_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Try to initialize Operator without initializing config first + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + + let config = Config::find_program_address(&jito_restaking_program::id()).0; + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + + let result = restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await; + + // TODO (LB): check specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_initialize_multiple_operators_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize first operator + let operator_admin1 = Keypair::new(); + let operator_base1 = Keypair::new(); + fixture + .transfer(&operator_admin1.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey1 = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base1.pubkey()) + .0; + + restaking_program_client + .initialize_operator( + &config, + &operator_pubkey1, + &operator_admin1, + &operator_base1, + ) + .await + .unwrap(); + + // Initialize second operator + let operator_admin2 = Keypair::new(); + let operator_base2 = Keypair::new(); + fixture + .transfer(&operator_admin2.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey2 = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base2.pubkey()) + .0; + + restaking_program_client + .initialize_operator( + &config, + &operator_pubkey2, + &operator_admin2, + &operator_base2, + ) + .await + .unwrap(); + + // Verify operator accounts + let operator1 = restaking_program_client + .get_operator(&operator_pubkey1) + .await + .unwrap(); + let operator2 = restaking_program_client + .get_operator(&operator_pubkey2) + .await + .unwrap(); + + assert_eq!(operator1.index(), 0); + assert_eq!(operator2.index(), 1); + + // Verify config update + let updated_config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(updated_config.operators_count(), 2); + } } diff --git a/integration_tests/tests/restaking/operator_add_avs.rs b/integration_tests/tests/restaking/operator_add_avs.rs index 680ab0de..82458d7a 100644 --- a/integration_tests/tests/restaking/operator_add_avs.rs +++ b/integration_tests/tests/restaking/operator_add_avs.rs @@ -1,478 +1,490 @@ -use jito_restaking_core::{ - avs::Avs, config::Config, operator::Operator, operator_avs_ticket::OperatorAvsTicket, -}; -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_operator_add_avs_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - // Operator adds AVS - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - - restaking_program_client - .operator_add_avs( - &config, +#[cfg(test)] +mod tests { + use jito_restaking_core::{ + avs::Avs, config::Config, operator::Operator, operator_avs_ticket::OperatorAvsTicket, + }; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_operator_add_avs_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + // Operator adds AVS + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // Verify operator state - let operator = restaking_program_client - .get_operator(&operator_pubkey) - .await - .unwrap(); - assert_eq!(operator.avs_count(), 1); - - // Verify operator AVS ticket - let ticket = restaking_program_client - .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey) - .await - .unwrap(); - assert_eq!(ticket.operator(), operator_pubkey); - assert_eq!(ticket.avs(), avs_pubkey); - assert_eq!(ticket.index(), 0); - assert_eq!(ticket.state().slot_added(), 1); -} - -#[tokio::test] -async fn test_operator_add_multiple_avs_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize two AVSs - let avs_admin1 = Keypair::new(); - let avs_base1 = Keypair::new(); - fixture.transfer(&avs_admin1.pubkey(), 10.0).await.unwrap(); - let avs_pubkey1 = - Avs::find_program_address(&jito_restaking_program::id(), &avs_base1.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey1, &avs_admin1, &avs_base1) - .await - .unwrap(); - - let avs_admin2 = Keypair::new(); - let avs_base2 = Keypair::new(); - fixture.transfer(&avs_admin2.pubkey(), 10.0).await.unwrap(); - let avs_pubkey2 = - Avs::find_program_address(&jito_restaking_program::id(), &avs_base2.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey2, &avs_admin2, &avs_base2) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Operator adds first AVS - let operator_avs_ticket1 = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey1, - ) - .0; - - restaking_program_client - .operator_add_avs( - &config, + .0; + + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // Verify operator state + let operator = restaking_program_client + .get_operator(&operator_pubkey) + .await + .unwrap(); + assert_eq!(operator.avs_count(), 1); + + // Verify operator AVS ticket + let ticket = restaking_program_client + .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey) + .await + .unwrap(); + assert_eq!(ticket.operator(), operator_pubkey); + assert_eq!(ticket.avs(), avs_pubkey); + assert_eq!(ticket.index(), 0); + assert_eq!(ticket.state().slot_added(), 1); + } + + #[tokio::test] + async fn test_operator_add_multiple_avs_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize two AVSs + let avs_admin1 = Keypair::new(); + let avs_base1 = Keypair::new(); + fixture.transfer(&avs_admin1.pubkey(), 10.0).await.unwrap(); + let avs_pubkey1 = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base1.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey1, &avs_admin1, &avs_base1) + .await + .unwrap(); + + let avs_admin2 = Keypair::new(); + let avs_base2 = Keypair::new(); + fixture.transfer(&avs_admin2.pubkey(), 10.0).await.unwrap(); + let avs_pubkey2 = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base2.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey2, &avs_admin2, &avs_base2) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Operator adds first AVS + let operator_avs_ticket1 = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey1, - &operator_avs_ticket1, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // Operator adds second AVS - let operator_avs_ticket2 = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey2, - ) - .0; - - restaking_program_client - .operator_add_avs( - &config, + .0; + + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey1, + &operator_avs_ticket1, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // Operator adds second AVS + let operator_avs_ticket2 = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey2, - &operator_avs_ticket2, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // Verify operator state - let operator = restaking_program_client - .get_operator(&operator_pubkey) - .await - .unwrap(); - assert_eq!(operator.avs_count(), 2); - - // Verify operator AVS tickets - let ticket1 = restaking_program_client - .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey1) - .await - .unwrap(); - assert_eq!(ticket1.operator(), operator_pubkey); - assert_eq!(ticket1.avs(), avs_pubkey1); - assert_eq!(ticket1.index(), 0); - - let ticket2 = restaking_program_client - .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey2) - .await - .unwrap(); - assert_eq!(ticket2.operator(), operator_pubkey); - assert_eq!(ticket2.avs(), avs_pubkey2); - assert_eq!(ticket2.index(), 1); -} - -#[tokio::test] -async fn test_operator_add_avs_duplicate_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - - // Operator adds AVS for the first time - restaking_program_client - .operator_add_avs( - &config, + .0; + + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey2, + &operator_avs_ticket2, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // Verify operator state + let operator = restaking_program_client + .get_operator(&operator_pubkey) + .await + .unwrap(); + assert_eq!(operator.avs_count(), 2); + + // Verify operator AVS tickets + let ticket1 = restaking_program_client + .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey1) + .await + .unwrap(); + assert_eq!(ticket1.operator(), operator_pubkey); + assert_eq!(ticket1.avs(), avs_pubkey1); + assert_eq!(ticket1.index(), 0); + + let ticket2 = restaking_program_client + .get_operator_avs_ticket(&operator_pubkey, &avs_pubkey2) + .await + .unwrap(); + assert_eq!(ticket2.operator(), operator_pubkey); + assert_eq!(ticket2.avs(), avs_pubkey2); + assert_eq!(ticket2.index(), 1); + } + + #[tokio::test] + async fn test_operator_add_avs_duplicate_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await - .unwrap(); - - // Attempt to add the same AVS again - let result = restaking_program_client - .operator_add_avs( - &config, + .0; + + // Operator adds AVS for the first time + restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await + .unwrap(); + + // Attempt to add the same AVS again + let result = restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_operator_add_avs_non_admin_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_operator_add_avs_non_admin_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &avs_pubkey, - ) - .0; - - // Attempt to add AVS with non-admin signer - let non_admin = Keypair::new(); - fixture.transfer(&non_admin.pubkey(), 10.0).await.unwrap(); - - let result = restaking_program_client - .operator_add_avs( - &config, - &operator_pubkey, - &avs_pubkey, - &operator_avs_ticket, - &non_admin, - &payer, + .0; + + // Attempt to add AVS with non-admin signer + let non_admin = Keypair::new(); + fixture.transfer(&non_admin.pubkey(), 10.0).await.unwrap(); + + let result = restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &non_admin, + &payer, + ) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_operator_add_avs_uninitialized_operator_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Use uninitialized operator + let uninitialized_operator = Keypair::new(); + let uninitialized_operator_pubkey = Operator::find_program_address( + &jito_restaking_program::id(), + &uninitialized_operator.pubkey(), ) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} + .0; -#[tokio::test] -async fn test_operator_add_avs_uninitialized_operator_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Use uninitialized operator - let uninitialized_operator = Keypair::new(); - let uninitialized_operator_pubkey = Operator::find_program_address( - &jito_restaking_program::id(), - &uninitialized_operator.pubkey(), - ) - .0; - - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &uninitialized_operator_pubkey, - &avs_pubkey, - ) - .0; - - let result = restaking_program_client - .operator_add_avs( - &config, + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &uninitialized_operator_pubkey, &avs_pubkey, - &operator_avs_ticket, - &uninitialized_operator, - &payer, ) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); -} - -#[tokio::test] -async fn test_operator_add_avs_uninitialized_avs_fails() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); - - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); - - // Initialize operator - let operator_admin = Keypair::new(); - let operator_base = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) - .await - .unwrap(); - - let payer = Keypair::new(); - fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); - - // Use uninitialized AVS - let uninitialized_avs = Keypair::new(); - let uninitialized_avs_pubkey = - Avs::find_program_address(&jito_restaking_program::id(), &uninitialized_avs.pubkey()).0; - - let operator_avs_ticket = OperatorAvsTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &uninitialized_avs_pubkey, - ) - .0; - - let result = restaking_program_client - .operator_add_avs( - &config, + .0; + + let result = restaking_program_client + .operator_add_avs( + &config, + &uninitialized_operator_pubkey, + &avs_pubkey, + &operator_avs_ticket, + &uninitialized_operator, + &payer, + ) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_operator_add_avs_uninitialized_avs_fails() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Initialize operator + let operator_admin = Keypair::new(); + let operator_base = Keypair::new(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &operator_base.pubkey()) + .0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &operator_base) + .await + .unwrap(); + + let payer = Keypair::new(); + fixture.transfer(&payer.pubkey(), 10.0).await.unwrap(); + + // Use uninitialized AVS + let uninitialized_avs = Keypair::new(); + let uninitialized_avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &uninitialized_avs.pubkey()).0; + + let operator_avs_ticket = OperatorAvsTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &uninitialized_avs_pubkey, - &operator_avs_ticket, - &operator_admin, - &payer, ) - .await; - - // TODO (LB): check for specific error - assert!(result.is_err()); + .0; + + let result = restaking_program_client + .operator_add_avs( + &config, + &operator_pubkey, + &uninitialized_avs_pubkey, + &operator_avs_ticket, + &operator_admin, + &payer, + ) + .await; + + // TODO (LB): check for specific error + assert!(result.is_err()); + } } diff --git a/integration_tests/tests/restaking/operator_add_vault.rs b/integration_tests/tests/restaking/operator_add_vault.rs index 9ab343f3..f83ad1c9 100644 --- a/integration_tests/tests/restaking/operator_add_vault.rs +++ b/integration_tests/tests/restaking/operator_add_vault.rs @@ -1,91 +1,95 @@ -use jito_restaking_core::{ - avs::Avs, config::Config, operator::Operator, operator_vault_ticket::OperatorVaultTicket, -}; -use jito_vault_core::vault::Vault; -use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; +#[cfg(test)] +mod tests { + use jito_restaking_core::{ + avs::Avs, config::Config, operator::Operator, operator_vault_ticket::OperatorVaultTicket, + }; + use jito_vault_core::vault::Vault; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; -use crate::fixtures::fixture::TestBuilder; + use crate::fixtures::fixture::TestBuilder; -#[tokio::test] -async fn test_operator_add_vault_ok() { - let mut fixture = TestBuilder::new().await; - let mut restaking_program_client = fixture.restaking_program_client(); + #[tokio::test] + async fn test_operator_add_vault_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); - // Initialize config - let config_admin = Keypair::new(); - let config = Config::find_program_address(&jito_restaking_program::id()).0; - fixture - .transfer(&config_admin.pubkey(), 10.0) - .await - .unwrap(); - restaking_program_client - .initialize_config(&config, &config_admin) - .await - .unwrap(); + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); - // Initialize AVS - let avs_admin = Keypair::new(); - let avs_base = Keypair::new(); - fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); - let avs_pubkey = Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; - restaking_program_client - .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) - .await - .unwrap(); + // Initialize AVS + let avs_admin = Keypair::new(); + let avs_base = Keypair::new(); + fixture.transfer(&avs_admin.pubkey(), 10.0).await.unwrap(); + let avs_pubkey = + Avs::find_program_address(&jito_restaking_program::id(), &avs_base.pubkey()).0; + restaking_program_client + .initialize_avs(&config, &avs_pubkey, &avs_admin, &avs_base) + .await + .unwrap(); - // Initialize operator - let base = Keypair::new(); - let operator_admin = Keypair::new(); + // Initialize operator + let base = Keypair::new(); + let operator_admin = Keypair::new(); - fixture - .transfer(&operator_admin.pubkey(), 10.0) - .await - .unwrap(); + fixture + .transfer(&operator_admin.pubkey(), 10.0) + .await + .unwrap(); - let operator_pubkey = - Operator::find_program_address(&jito_restaking_program::id(), &base.pubkey()).0; - restaking_program_client - .initialize_operator(&config, &operator_pubkey, &operator_admin, &base) - .await - .unwrap(); + let operator_pubkey = + Operator::find_program_address(&jito_restaking_program::id(), &base.pubkey()).0; + restaking_program_client + .initialize_operator(&config, &operator_pubkey, &operator_admin, &base) + .await + .unwrap(); - let vault_pubkey = - Vault::find_program_address(&jito_restaking_program::id(), &Pubkey::new_unique()).0; + let vault_pubkey = + Vault::find_program_address(&jito_restaking_program::id(), &Pubkey::new_unique()).0; - // Operator adds vault - let operator_vault_ticket = OperatorVaultTicket::find_program_address( - &jito_restaking_program::id(), - &operator_pubkey, - &vault_pubkey, - ) - .0; - restaking_program_client - .operator_add_vault( - &config, + // Operator adds vault + let operator_vault_ticket = OperatorVaultTicket::find_program_address( + &jito_restaking_program::id(), &operator_pubkey, &vault_pubkey, - &operator_vault_ticket, - &operator_admin, - &operator_admin, ) - .await - .unwrap(); + .0; + restaking_program_client + .operator_add_vault( + &config, + &operator_pubkey, + &vault_pubkey, + &operator_vault_ticket, + &operator_admin, + &operator_admin, + ) + .await + .unwrap(); - // Verify operator state - let operator = restaking_program_client - .get_operator(&operator_pubkey) - .await - .unwrap(); - assert_eq!(operator.vault_count(), 1); + // Verify operator state + let operator = restaking_program_client + .get_operator(&operator_pubkey) + .await + .unwrap(); + assert_eq!(operator.vault_count(), 1); - // Verify operator vault ticket - let ticket = restaking_program_client - .get_operator_vault_ticket(&operator_pubkey, &vault_pubkey) - .await - .unwrap(); - assert_eq!(ticket.operator(), operator_pubkey); - assert_eq!(ticket.vault(), vault_pubkey); - assert_eq!(ticket.index(), 0); - assert_eq!(ticket.state().slot_added(), 1); + // Verify operator vault ticket + let ticket = restaking_program_client + .get_operator_vault_ticket(&operator_pubkey, &vault_pubkey) + .await + .unwrap(); + assert_eq!(ticket.operator(), operator_pubkey); + assert_eq!(ticket.vault(), vault_pubkey); + assert_eq!(ticket.index(), 0); + assert_eq!(ticket.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/vault/add_avs.rs b/integration_tests/tests/vault/add_avs.rs index 3811de06..b7de548b 100644 --- a/integration_tests/tests/vault/add_avs.rs +++ b/integration_tests/tests/vault/add_avs.rs @@ -1,34 +1,37 @@ -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_add_avs_ok() { - let fixture = TestBuilder::new().await; - - let mut restaking_program_client = fixture.restaking_program_client(); - let mut vault_program_client = fixture.vault_program_client(); - - let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - - let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - - let avs_root = restaking_program_client.setup_avs().await.unwrap(); - - restaking_program_client - .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - .await - .unwrap(); - - vault_program_client - .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - .await - .unwrap(); - - let vault_avs_ticket_account = vault_program_client - .get_vault_avs_ticket(&vault_root.vault_pubkey, &avs_root.avs_pubkey) - .await - .unwrap(); - assert_eq!(vault_avs_ticket_account.vault(), vault_root.vault_pubkey); - assert_eq!(vault_avs_ticket_account.avs(), avs_root.avs_pubkey); - assert_eq!(vault_avs_ticket_account.index(), 0); - assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); +#[cfg(test)] +mod tests { + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_add_avs_ok() { + let fixture = TestBuilder::new().await; + + let mut restaking_program_client = fixture.restaking_program_client(); + let mut vault_program_client = fixture.vault_program_client(); + + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); + + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + + let vault_avs_ticket_account = vault_program_client + .get_vault_avs_ticket(&vault_root.vault_pubkey, &avs_root.avs_pubkey) + .await + .unwrap(); + assert_eq!(vault_avs_ticket_account.vault(), vault_root.vault_pubkey); + assert_eq!(vault_avs_ticket_account.avs(), avs_root.avs_pubkey); + assert_eq!(vault_avs_ticket_account.index(), 0); + assert_eq!(vault_avs_ticket_account.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/vault/add_operator.rs b/integration_tests/tests/vault/add_operator.rs index 60147478..7af38660 100644 --- a/integration_tests/tests/vault/add_operator.rs +++ b/integration_tests/tests/vault/add_operator.rs @@ -1,37 +1,40 @@ -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_add_operator_ok() { - let fixture = TestBuilder::new().await; - - let mut restaking_program_client = fixture.restaking_program_client(); - let mut vault_program_client = fixture.vault_program_client(); - - let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - - let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - - let operator_root = restaking_program_client.setup_operator().await.unwrap(); - - restaking_program_client - .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) - .await - .unwrap(); - - vault_program_client - .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - let vault_operator_ticket = vault_program_client - .get_vault_operator_ticket(&vault_root.vault_pubkey, &operator_root.operator_pubkey) - .await - .unwrap(); - assert_eq!(vault_operator_ticket.vault(), vault_root.vault_pubkey); - assert_eq!( - vault_operator_ticket.operator(), - operator_root.operator_pubkey - ); - assert_eq!(vault_operator_ticket.index(), 0); - assert_eq!(vault_operator_ticket.state().slot_added(), 1); +#[cfg(test)] +mod tests { + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_add_operator_ok() { + let fixture = TestBuilder::new().await; + + let mut restaking_program_client = fixture.restaking_program_client(); + let mut vault_program_client = fixture.vault_program_client(); + + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); + + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + let operator_root = restaking_program_client.setup_operator().await.unwrap(); + + restaking_program_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault_operator_ticket = vault_program_client + .get_vault_operator_ticket(&vault_root.vault_pubkey, &operator_root.operator_pubkey) + .await + .unwrap(); + assert_eq!(vault_operator_ticket.vault(), vault_root.vault_pubkey); + assert_eq!( + vault_operator_ticket.operator(), + operator_root.operator_pubkey + ); + assert_eq!(vault_operator_ticket.index(), 0); + assert_eq!(vault_operator_ticket.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/vault/add_slasher.rs b/integration_tests/tests/vault/add_slasher.rs index 74b6a814..c065388f 100644 --- a/integration_tests/tests/vault/add_slasher.rs +++ b/integration_tests/tests/vault/add_slasher.rs @@ -1,53 +1,56 @@ -use solana_sdk::signature::{Keypair, Signer}; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_add_slasher_ok() { - let fixture = TestBuilder::new().await; - - let mut restaking_program_client = fixture.restaking_program_client(); - let mut vault_program_client = fixture.vault_program_client(); - - let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); - - let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - - let avs_root = restaking_program_client.setup_avs().await.unwrap(); - - restaking_program_client - .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - .await - .unwrap(); - - vault_program_client - .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - .await - .unwrap(); - - let slasher = Keypair::new(); - restaking_program_client - .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) - .await - .unwrap(); - - vault_program_client - .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) - .await - .unwrap(); - - let vault_avs_slasher = vault_program_client - .get_vault_avs_slasher_ticket( - &vault_root.vault_pubkey, - &avs_root.avs_pubkey, - &slasher.pubkey(), - ) - .await - .unwrap(); - assert_eq!(vault_avs_slasher.vault(), vault_root.vault_pubkey); - assert_eq!(vault_avs_slasher.avs(), avs_root.avs_pubkey); - assert_eq!(vault_avs_slasher.slasher(), slasher.pubkey()); - assert_eq!(vault_avs_slasher.index(), 0); - assert_eq!(vault_avs_slasher.max_slashable_per_epoch(), 100); - assert_eq!(vault_avs_slasher.state().slot_added(), 1); +#[cfg(test)] +mod tests { + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_add_slasher_ok() { + let fixture = TestBuilder::new().await; + + let mut restaking_program_client = fixture.restaking_program_client(); + let mut vault_program_client = fixture.vault_program_client(); + + let (_config_admin, vault_root) = vault_program_client.setup_vault(99, 100).await.unwrap(); + + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + + let slasher = Keypair::new(); + restaking_program_client + .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) + .await + .unwrap(); + + vault_program_client + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) + .await + .unwrap(); + + let vault_avs_slasher = vault_program_client + .get_vault_avs_slasher_ticket( + &vault_root.vault_pubkey, + &avs_root.avs_pubkey, + &slasher.pubkey(), + ) + .await + .unwrap(); + assert_eq!(vault_avs_slasher.vault(), vault_root.vault_pubkey); + assert_eq!(vault_avs_slasher.avs(), avs_root.avs_pubkey); + assert_eq!(vault_avs_slasher.slasher(), slasher.pubkey()); + assert_eq!(vault_avs_slasher.index(), 0); + assert_eq!(vault_avs_slasher.max_slashable_per_epoch(), 100); + assert_eq!(vault_avs_slasher.state().slot_added(), 1); + } } diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs index e69de29b..58d9a765 100644 --- a/integration_tests/tests/vault/burn_withdrawal_ticket.rs +++ b/integration_tests/tests/vault/burn_withdrawal_ticket.rs @@ -0,0 +1,260 @@ +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use solana_program::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; + use spl_associated_token_account::get_associated_token_address; + + use crate::fixtures::{ + fixture::TestBuilder, + restaking_client::{AvsRoot, OperatorRoot, RestakingProgramClient}, + vault_client::{VaultProgramClient, VaultRoot, VaultStakerWithdrawalTicketRoot}, + }; + + struct PreparedWithdrawalTicket { + vault_config_admin: Keypair, + vault_root: VaultRoot, + avs_root: AvsRoot, + operator_root: OperatorRoot, + depositor: Keypair, + withdrawal_ticket_base: Pubkey, + } + + async fn setup_withdrawal_ticket( + fixture: &mut TestBuilder, + vault_program_client: &mut VaultProgramClient, + restaking_program_client: &mut RestakingProgramClient, + deposit_fee_bps: u16, + withdraw_fee_bps: u16, + mint_amount: u64, + deposit_amount: u64, + delegate_amount: u64, + withdrawal_amount: u64, + ) -> PreparedWithdrawalTicket { + // Setup vault with initial deposit + let (vault_config_admin, vault_root) = vault_program_client + .setup_vault(deposit_fee_bps, withdraw_fee_bps) + .await + .unwrap(); + let restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + // Setup operator and AVS + let operator_root = restaking_program_client.setup_operator().await.unwrap(); + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + + // Setup necessary relationships + restaking_program_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + restaking_program_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // Initial deposit + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), mint_amount) + .await + .unwrap(); + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + // Mint LRT tokens to depositor + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + deposit_amount, + ) + .await + .unwrap(); + + // Delegate all funds to the operator + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, delegate_amount) + .await + .unwrap(); + + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, withdrawal_amount) + .await + .unwrap(); + + PreparedWithdrawalTicket { + vault_config_admin, + vault_root, + avs_root, + operator_root, + depositor, + withdrawal_ticket_base: base, + } + } + + #[tokio::test] + async fn test_burn_withdrawal_ticket_same_epoch_fails() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_config_admin, + vault_root, + avs_root, + operator_root, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 100, + 100, + 100, + ) + .await; + + // TODO (LB): check error type + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap_err(); + } + + #[tokio::test] + async fn test_burn_withdrawal_ticket_next_epoch_fails() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_config_admin, + vault_root, + avs_root, + operator_root, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 100, + 100, + 100, + ) + .await; + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + fixture + .warp_slot_incremental(config.epoch_length()) + .await + .unwrap(); + + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap_err(); + } + + #[tokio::test] + async fn test_burn_withdrawal_ticket_basic_success() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_config_admin, + vault_root, + avs_root, + operator_root, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 100, + 100, + 100, + ) + .await; + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap(); + } + + #[tokio::test] + async fn test_burn_withdrawal_ticket_with_unstaked_rewards() {} + + #[tokio::test] + async fn test_burn_withdrawal_ticket_with_staked_rewards() {} + + #[tokio::test] + async fn test_burn_withdrawal_ticket_with_slashing() {} +} diff --git a/integration_tests/tests/vault/enqueue_withdrawal.rs b/integration_tests/tests/vault/enqueue_withdrawal.rs index 62d2919b..a3795e99 100644 --- a/integration_tests/tests/vault/enqueue_withdrawal.rs +++ b/integration_tests/tests/vault/enqueue_withdrawal.rs @@ -1,314 +1,326 @@ -use solana_sdk::signature::{Keypair, Signer}; -use spl_associated_token_account::get_associated_token_address; - -use crate::fixtures::{fixture::TestBuilder, vault_client::VaultStakerWithdrawalTicketRoot}; - -#[tokio::test] -async fn test_enqueue_withdraw_more_than_staked_fails() { - let mut fixture = TestBuilder::new().await; - - let mut vault_program_client = fixture.vault_program_client(); - - let (_vault_config_admin, vault_root) = - vault_program_client.setup_vault(100, 100).await.unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - - let depositor = Keypair::new(); - fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); - fixture - .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) - .await - .unwrap(); - - fixture - .create_ata(&vault.lrt_mint(), &depositor.pubkey()) - .await - .unwrap(); - - let depositor_lrt_token_account = - get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); - vault_program_client - .mint_to( - &vault_root.vault_pubkey, - &vault.lrt_mint(), - &depositor, - &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), - &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), - &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), - &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), - None, - 100_000, - ) - .await - .unwrap(); - - let depositor_ata = fixture - .get_token_account(&depositor_lrt_token_account) - .await - .unwrap(); - assert_eq!(depositor_ata.amount, 99_000); - - vault_program_client - .do_enqueue_withdraw(&vault_root, &depositor, 49_500) - .await - .unwrap_err(); -} - -#[tokio::test] -async fn test_enqueue_withdraw_with_fee_success() { - let mut fixture = TestBuilder::new().await; - - let mut vault_program_client = fixture.vault_program_client(); - let mut restaking_pool_client = fixture.restaking_program_client(); - - let (_vault_config_admin, vault_root) = - vault_program_client.setup_vault(100, 100).await.unwrap(); - - let _restaking_config_admin = restaking_pool_client.setup_config().await.unwrap(); - - let operator_root = restaking_pool_client.setup_operator().await.unwrap(); - let avs_root = restaking_pool_client.setup_avs().await.unwrap(); - - restaking_pool_client - .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) - .await - .unwrap(); - restaking_pool_client - .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - restaking_pool_client - .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - .await - .unwrap(); - restaking_pool_client - .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) - .await - .unwrap(); - - vault_program_client - .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - .await - .unwrap(); - vault_program_client - .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - - let depositor = Keypair::new(); - fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); - fixture - .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) - .await - .unwrap(); - - fixture - .create_ata(&vault.lrt_mint(), &depositor.pubkey()) - .await - .unwrap(); - - vault_program_client - .mint_to( - &vault_root.vault_pubkey, - &vault.lrt_mint(), - &depositor, - &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), - &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), - &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), - &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), - None, - 100_000, - ) - .await - .unwrap(); - - let vault_lrt_account = fixture - .get_token_account(&get_associated_token_address( - &depositor.pubkey(), - &vault.lrt_mint(), - )) - .await - .unwrap(); - assert_eq!(vault_lrt_account.amount, 99_000); - - let vault_fee_account = fixture - .get_token_account(&get_associated_token_address( - &vault.fee_owner(), - &vault.lrt_mint(), - )) - .await - .unwrap(); - assert_eq!(vault_fee_account.amount, 1_000); - - vault_program_client - .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) - .await - .unwrap(); - - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - - let delegation = vault_delegation_list.delegations().get(0).unwrap(); - assert_eq!(delegation.staked_amount(), 100_000); - assert_eq!(delegation.total_security().unwrap(), 100_000); - - // the user is withdrawing 99,000 LRT tokens, there is a 1% fee on withdraws, so - // 98010 tokens will be undeleged for withdraw - let VaultStakerWithdrawalTicketRoot { base } = vault_program_client - .do_enqueue_withdraw(&vault_root, &depositor, 99_000) - .await - .unwrap(); - - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - - let delegation = vault_delegation_list.delegations().get(0).unwrap(); - // this is 1,000 because 1% of the fee went to the vault fee account, the assets still staked - // are for the LRT in the fee account to unstake later - assert_eq!(delegation.staked_amount(), 1_990); - assert_eq!(delegation.enqueued_for_withdraw_amount(), 98_010); - assert_eq!(delegation.total_security().unwrap(), 100_000); - - let vault_staker_withdrawal_ticket = vault_program_client - .get_vault_staker_withdrawal_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) - .await - .unwrap(); - assert_eq!(vault_staker_withdrawal_ticket.lrt_amount(), 98_010); - assert_eq!( - vault_staker_withdrawal_ticket.withdraw_allocation_amount(), - 98_010 - ); -} - -#[tokio::test] -async fn test_enqueue_withdraw_with_reward_ok() { - let mut fixture = TestBuilder::new().await; - let mut vault_program_client = fixture.vault_program_client(); - let mut restaking_program_client = fixture.restaking_program_client(); - - // Setup vault with initial deposit - let (_vault_config_admin, vault_root) = vault_program_client.setup_vault(0, 0).await.unwrap(); - let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - - // Setup operator and AVS - let operator_root = restaking_program_client.setup_operator().await.unwrap(); - let avs_root = restaking_program_client.setup_avs().await.unwrap(); - - // Setup necessary relationships - restaking_program_client - .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) - .await - .unwrap(); - restaking_program_client - .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) - .await - .unwrap(); - restaking_program_client - .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - .await - .unwrap(); - restaking_program_client - .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) - .await - .unwrap(); - vault_program_client - .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - .await - .unwrap(); - vault_program_client - .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - - // Initial deposit - let depositor = Keypair::new(); - fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); - fixture - .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) - .await - .unwrap(); - fixture - .create_ata(&vault.lrt_mint(), &depositor.pubkey()) - .await - .unwrap(); - - // Mint LRT tokens to depositor - vault_program_client - .mint_to( - &vault_root.vault_pubkey, - &vault.lrt_mint(), - &depositor, - &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), - &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), - &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), - &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), - None, - 100_000, - ) - .await - .unwrap(); - - // Delegate all funds to the operator - vault_program_client - .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) - .await - .unwrap(); - - // Simulate rewards by adding more tokens to the vault - fixture - .mint_to(&vault.supported_mint(), &vault_root.vault_pubkey, 10_000) - .await - .unwrap(); - vault_program_client - .do_update_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - - // Enqueue withdrawal for half of the original deposit - let withdraw_amount = 50_000; - let VaultStakerWithdrawalTicketRoot { base } = vault_program_client - .do_enqueue_withdraw(&vault_root, &depositor, withdraw_amount) - .await - .unwrap(); - - // Verify the withdraw ticket - let withdrawal_ticket = vault_program_client - .get_vault_staker_withdrawal_ticket(&vault_root.vault_pubkey, &depositor.pubkey(), &base) - .await - .unwrap(); - - assert_eq!(withdrawal_ticket.lrt_amount(), withdraw_amount); - - // The actual assets to be withdrawn should be more than the LRT amount due to rewards - assert_eq!(withdrawal_ticket.withdraw_allocation_amount(), 55_000); - - // Verify the vault delegation list - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - - let delegation = vault_delegation_list.delegations().get(0).unwrap(); - assert_eq!(delegation.staked_amount(), 45_000); - assert_eq!(delegation.enqueued_for_withdraw_amount(), 55_000); - assert_eq!(delegation.total_security().unwrap(), 100_000); +#[cfg(test)] +mod tests { + use solana_sdk::signature::{Keypair, Signer}; + use spl_associated_token_account::get_associated_token_address; + + use crate::fixtures::{fixture::TestBuilder, vault_client::VaultStakerWithdrawalTicketRoot}; + + #[tokio::test] + async fn test_enqueue_withdraw_more_than_staked_fails() { + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + + let (_vault_config_admin, vault_root) = + vault_program_client.setup_vault(100, 100).await.unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + let depositor_lrt_token_account = + get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()); + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + let depositor_ata = fixture + .get_token_account(&depositor_lrt_token_account) + .await + .unwrap(); + assert_eq!(depositor_ata.amount, 99_000); + + vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, 49_500) + .await + .unwrap_err(); + } + + #[tokio::test] + async fn test_enqueue_withdraw_with_fee_success() { + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_pool_client = fixture.restaking_program_client(); + + let (_vault_config_admin, vault_root) = + vault_program_client.setup_vault(100, 100).await.unwrap(); + + let _restaking_config_admin = restaking_pool_client.setup_config().await.unwrap(); + + let operator_root = restaking_pool_client.setup_operator().await.unwrap(); + let avs_root = restaking_pool_client.setup_avs().await.unwrap(); + + restaking_pool_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_pool_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + restaking_pool_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + restaking_pool_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + let vault_lrt_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_lrt_account.amount, 99_000); + + let vault_fee_account = fixture + .get_token_account(&get_associated_token_address( + &vault.fee_owner(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_fee_account.amount, 1_000); + + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 100_000); + assert_eq!(delegation.total_security().unwrap(), 100_000); + + // the user is withdrawing 99,000 LRT tokens, there is a 1% fee on withdraws, so + // 98010 tokens will be undeleged for withdraw + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, 99_000) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + // this is 1,000 because 1% of the fee went to the vault fee account, the assets still staked + // are for the LRT in the fee account to unstake later + assert_eq!(delegation.staked_amount(), 1_990); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 98_010); + assert_eq!(delegation.total_security().unwrap(), 100_000); + + let vault_staker_withdrawal_ticket = vault_program_client + .get_vault_staker_withdrawal_ticket( + &vault_root.vault_pubkey, + &depositor.pubkey(), + &base, + ) + .await + .unwrap(); + assert_eq!(vault_staker_withdrawal_ticket.lrt_amount(), 98_010); + assert_eq!( + vault_staker_withdrawal_ticket.withdraw_allocation_amount(), + 98_010 + ); + } + + #[tokio::test] + async fn test_enqueue_withdraw_with_reward_ok() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + // Setup vault with initial deposit + let (_vault_config_admin, vault_root) = + vault_program_client.setup_vault(0, 0).await.unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + // Setup operator and AVS + let operator_root = restaking_program_client.setup_operator().await.unwrap(); + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + + // Setup necessary relationships + restaking_program_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + restaking_program_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // Initial deposit + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 100.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + // Mint LRT tokens to depositor + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + // Delegate all funds to the operator + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 100_000) + .await + .unwrap(); + + // Simulate rewards by adding more tokens to the vault + fixture + .mint_to(&vault.supported_mint(), &vault_root.vault_pubkey, 10_000) + .await + .unwrap(); + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // Enqueue withdrawal for half of the original deposit + let withdraw_amount = 50_000; + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client + .do_enqueue_withdraw(&vault_root, &depositor, withdraw_amount) + .await + .unwrap(); + + // Verify the withdraw ticket + let withdrawal_ticket = vault_program_client + .get_vault_staker_withdrawal_ticket( + &vault_root.vault_pubkey, + &depositor.pubkey(), + &base, + ) + .await + .unwrap(); + + assert_eq!(withdrawal_ticket.lrt_amount(), withdraw_amount); + + // The actual assets to be withdrawn should be more than the LRT amount due to rewards + assert_eq!(withdrawal_ticket.withdraw_allocation_amount(), 55_000); + + // Verify the vault delegation list + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegation = vault_delegation_list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 45_000); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 55_000); + assert_eq!(delegation.total_security().unwrap(), 100_000); + } } diff --git a/integration_tests/tests/vault/initialize_config.rs b/integration_tests/tests/vault/initialize_config.rs index 969dc711..c3d330cc 100644 --- a/integration_tests/tests/vault/initialize_config.rs +++ b/integration_tests/tests/vault/initialize_config.rs @@ -1,22 +1,25 @@ -use jito_vault_core::config::Config; -use solana_sdk::signature::Signer; +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use solana_sdk::signature::Signer; -use crate::fixtures::fixture::TestBuilder; + use crate::fixtures::fixture::TestBuilder; -#[tokio::test] -async fn test_initialize_config_ok() { - let fixture = TestBuilder::new().await; - let mut vault_program_client = fixture.vault_program_client(); + #[tokio::test] + async fn test_initialize_config_ok() { + let fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); - let config_admin = vault_program_client.setup_config().await.unwrap(); + let config_admin = vault_program_client.setup_config().await.unwrap(); - let config = vault_program_client - .get_config(&Config::find_program_address(&jito_vault_program::id()).0) - .await - .unwrap(); + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); - assert_eq!(config.admin(), config_admin.pubkey()); - assert_eq!(config.restaking_program(), jito_restaking_program::id()); - assert_eq!(config.epoch_length(), 864_000); - assert_eq!(config.vaults_count(), 0); + assert_eq!(config.admin(), config_admin.pubkey()); + assert_eq!(config.restaking_program(), jito_restaking_program::id()); + assert_eq!(config.epoch_length(), 864_000); + assert_eq!(config.vaults_count(), 0); + } } diff --git a/integration_tests/tests/vault/initialize_vault.rs b/integration_tests/tests/vault/initialize_vault.rs index c366f3d4..c6a6593e 100644 --- a/integration_tests/tests/vault/initialize_vault.rs +++ b/integration_tests/tests/vault/initialize_vault.rs @@ -1,36 +1,39 @@ -use solana_sdk::signature::Signer; +#[cfg(test)] +mod tests { + use solana_sdk::signature::Signer; -use crate::fixtures::{fixture::TestBuilder, vault_client::VaultRoot}; + use crate::fixtures::{fixture::TestBuilder, vault_client::VaultRoot}; -#[tokio::test] -async fn test_initialize_vault_ok() { - let fixture = TestBuilder::new().await; + #[tokio::test] + async fn test_initialize_vault_ok() { + let fixture = TestBuilder::new().await; - let mut vault_program_client = fixture.vault_program_client(); + let mut vault_program_client = fixture.vault_program_client(); - let ( - _config_admin, - VaultRoot { - vault_pubkey, - vault_admin, - }, - ) = vault_program_client.setup_vault(99, 100).await.unwrap(); + let ( + _config_admin, + VaultRoot { + vault_pubkey, + vault_admin, + }, + ) = vault_program_client.setup_vault(99, 100).await.unwrap(); - let vault = vault_program_client.get_vault(&vault_pubkey).await.unwrap(); - assert_eq!(vault.admin(), vault_admin.pubkey()); - assert_eq!(vault.delegation_admin(), vault_admin.pubkey()); - assert_eq!(vault.operator_admin(), vault_admin.pubkey()); - assert_eq!(vault.avs_admin(), vault_admin.pubkey()); - assert_eq!(vault.slasher_admin(), vault_admin.pubkey()); - assert_eq!(vault.fee_owner(), vault_admin.pubkey()); - assert_eq!(vault.mint_burn_authority(), None); - assert_eq!(vault.capacity(), u64::MAX); - assert_eq!(vault.vault_index(), 0); - assert_eq!(vault.lrt_supply(), 0); - assert_eq!(vault.tokens_deposited(), 0); - assert_eq!(vault.deposit_fee_bps(), 99); - assert_eq!(vault.withdrawal_fee_bps(), 100); - assert_eq!(vault.avs_count(), 0); - assert_eq!(vault.operator_count(), 0); - assert_eq!(vault.slasher_count(), 0); + let vault = vault_program_client.get_vault(&vault_pubkey).await.unwrap(); + assert_eq!(vault.admin(), vault_admin.pubkey()); + assert_eq!(vault.delegation_admin(), vault_admin.pubkey()); + assert_eq!(vault.operator_admin(), vault_admin.pubkey()); + assert_eq!(vault.avs_admin(), vault_admin.pubkey()); + assert_eq!(vault.slasher_admin(), vault_admin.pubkey()); + assert_eq!(vault.fee_owner(), vault_admin.pubkey()); + assert_eq!(vault.mint_burn_authority(), None); + assert_eq!(vault.capacity(), u64::MAX); + assert_eq!(vault.vault_index(), 0); + assert_eq!(vault.lrt_supply(), 0); + assert_eq!(vault.tokens_deposited(), 0); + assert_eq!(vault.deposit_fee_bps(), 99); + assert_eq!(vault.withdrawal_fee_bps(), 100); + assert_eq!(vault.avs_count(), 0); + assert_eq!(vault.operator_count(), 0); + assert_eq!(vault.slasher_count(), 0); + } } diff --git a/integration_tests/tests/vault/slash.rs b/integration_tests/tests/vault/slash.rs index 82946f34..23b3cbc2 100644 --- a/integration_tests/tests/vault/slash.rs +++ b/integration_tests/tests/vault/slash.rs @@ -1,185 +1,188 @@ -use solana_sdk::signature::{Keypair, Signer}; -use spl_associated_token_account::get_associated_token_address; - -use crate::fixtures::fixture::TestBuilder; - -#[tokio::test] -async fn test_slash_ok() { - let mut fixture = TestBuilder::new().await; - - let mut restaking_program_client = fixture.restaking_program_client(); - let mut vault_program_client = fixture.vault_program_client(); - - let (_config_admin, vault_root) = vault_program_client.setup_vault(100, 100).await.unwrap(); - - let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); - - let avs_root = restaking_program_client.setup_avs().await.unwrap(); - let operator_root = restaking_program_client.setup_operator().await.unwrap(); - - // AVS <-> Vault - restaking_program_client - .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) - .await - .unwrap(); - vault_program_client - .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) - .await - .unwrap(); - - // AVS <-> Operator - // operator needs to opt-in first - restaking_program_client - .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) - .await - .unwrap(); - restaking_program_client - .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - // Vault <-> Operator - // operator needs to opt-in first - restaking_program_client - .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) - .await - .unwrap(); - vault_program_client - .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) - .await - .unwrap(); - - // AVS + vault configures slasher - let slasher = Keypair::new(); - fixture.transfer(&slasher.pubkey(), 1.0).await.unwrap(); - - restaking_program_client - .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) - .await - .unwrap(); - vault_program_client - .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) - .await - .unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - - let depositor = Keypair::new(); - fixture.transfer(&depositor.pubkey(), 1.0).await.unwrap(); - fixture - .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) - .await - .unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - // depositor ATA for LRT - fixture - .create_ata(&vault.lrt_mint(), &depositor.pubkey()) - .await - .unwrap(); - - vault_program_client - .mint_to( - &vault_root.vault_pubkey, - &vault.lrt_mint(), - &depositor, - &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), - &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), - &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), - &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), - None, - 100_000, - ) - .await - .unwrap(); - - // user has 99_000 because 100 bips deposit fee - - vault_program_client - .delegate(&vault_root, &operator_root.operator_pubkey, 10_000) - .await - .unwrap(); - - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - - let delegations = vault_delegation_list.delegations(); - assert_eq!(delegations.len(), 1); - assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); - assert_eq!(delegations[0].staked_amount(), 10_000); - - fixture - .create_ata(&vault.supported_mint(), &slasher.pubkey()) - .await - .unwrap(); - - vault_program_client - .setup_vault_avs_slasher_operator_ticket( - &vault_root, - &avs_root.avs_pubkey, - &slasher.pubkey(), - &operator_root.operator_pubkey, - ) - .await - .unwrap(); - - vault_program_client - .do_slash( - &vault_root, - &avs_root.avs_pubkey, - &slasher, - &operator_root.operator_pubkey, - 100, - ) - .await - .unwrap(); - - let vault = vault_program_client - .get_vault(&vault_root.vault_pubkey) - .await - .unwrap(); - assert_eq!(vault.tokens_deposited(), 99_900); - - let delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - let delegations = delegation_list.delegations(); - assert_eq!(delegations.len(), 1); - assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); - assert_eq!(delegations[0].staked_amount(), 9_900); - - let vault_avs_slasher_operator_ticket = vault_program_client - .get_vault_avs_slasher_operator_ticket( - &vault_root.vault_pubkey, - &avs_root.avs_pubkey, - &slasher.pubkey(), - &operator_root.operator_pubkey, - 0, - ) - .await - .unwrap(); - assert_eq!(vault_avs_slasher_operator_ticket.slashed(), 100); - assert_eq!(vault_avs_slasher_operator_ticket.epoch(), 0); - assert_eq!( - vault_avs_slasher_operator_ticket.vault(), - vault_root.vault_pubkey - ); - assert_eq!(vault_avs_slasher_operator_ticket.avs(), avs_root.avs_pubkey); - assert_eq!( - vault_avs_slasher_operator_ticket.slasher(), - slasher.pubkey() - ); - assert_eq!( - vault_avs_slasher_operator_ticket.operator(), - operator_root.operator_pubkey - ); +#[cfg(test)] +mod tests { + use solana_sdk::signature::{Keypair, Signer}; + use spl_associated_token_account::get_associated_token_address; + + use crate::fixtures::fixture::TestBuilder; + + #[tokio::test] + async fn test_slash_ok() { + let mut fixture = TestBuilder::new().await; + + let mut restaking_program_client = fixture.restaking_program_client(); + let mut vault_program_client = fixture.vault_program_client(); + + let (_config_admin, vault_root) = vault_program_client.setup_vault(100, 100).await.unwrap(); + + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + + let avs_root = restaking_program_client.setup_avs().await.unwrap(); + let operator_root = restaking_program_client.setup_operator().await.unwrap(); + + // AVS <-> Vault + restaking_program_client + .avs_vault_opt_in(&avs_root, &vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .vault_avs_opt_in(&vault_root, &avs_root.avs_pubkey) + .await + .unwrap(); + + // AVS <-> Operator + // operator needs to opt-in first + restaking_program_client + .operator_avs_opt_in(&operator_root, &avs_root.avs_pubkey) + .await + .unwrap(); + restaking_program_client + .avs_operator_opt_in(&avs_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + // Vault <-> Operator + // operator needs to opt-in first + restaking_program_client + .operator_vault_opt_in(&operator_root, &vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .vault_operator_opt_in(&vault_root, &operator_root.operator_pubkey) + .await + .unwrap(); + + // AVS + vault configures slasher + let slasher = Keypair::new(); + fixture.transfer(&slasher.pubkey(), 1.0).await.unwrap(); + + restaking_program_client + .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) + .await + .unwrap(); + vault_program_client + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor = Keypair::new(); + fixture.transfer(&depositor.pubkey(), 1.0).await.unwrap(); + fixture + .mint_to(&vault.supported_mint(), &depositor.pubkey(), 100_000) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + // depositor ATA for LRT + fixture + .create_ata(&vault.lrt_mint(), &depositor.pubkey()) + .await + .unwrap(); + + vault_program_client + .mint_to( + &vault_root.vault_pubkey, + &vault.lrt_mint(), + &depositor, + &get_associated_token_address(&depositor.pubkey(), &vault.supported_mint()), + &get_associated_token_address(&vault_root.vault_pubkey, &vault.supported_mint()), + &get_associated_token_address(&depositor.pubkey(), &vault.lrt_mint()), + &get_associated_token_address(&vault.fee_owner(), &vault.lrt_mint()), + None, + 100_000, + ) + .await + .unwrap(); + + // user has 99_000 because 100 bips deposit fee + + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 10_000) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + + let delegations = vault_delegation_list.delegations(); + assert_eq!(delegations.len(), 1); + assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); + assert_eq!(delegations[0].staked_amount(), 10_000); + + fixture + .create_ata(&vault.supported_mint(), &slasher.pubkey()) + .await + .unwrap(); + + vault_program_client + .setup_vault_avs_slasher_operator_ticket( + &vault_root, + &avs_root.avs_pubkey, + &slasher.pubkey(), + &operator_root.operator_pubkey, + ) + .await + .unwrap(); + + vault_program_client + .do_slash( + &vault_root, + &avs_root.avs_pubkey, + &slasher, + &operator_root.operator_pubkey, + 100, + ) + .await + .unwrap(); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + assert_eq!(vault.tokens_deposited(), 99_900); + + let delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + let delegations = delegation_list.delegations(); + assert_eq!(delegations.len(), 1); + assert_eq!(delegations[0].operator(), operator_root.operator_pubkey); + assert_eq!(delegations[0].staked_amount(), 9_900); + + let vault_avs_slasher_operator_ticket = vault_program_client + .get_vault_avs_slasher_operator_ticket( + &vault_root.vault_pubkey, + &avs_root.avs_pubkey, + &slasher.pubkey(), + &operator_root.operator_pubkey, + 0, + ) + .await + .unwrap(); + assert_eq!(vault_avs_slasher_operator_ticket.slashed(), 100); + assert_eq!(vault_avs_slasher_operator_ticket.epoch(), 0); + assert_eq!( + vault_avs_slasher_operator_ticket.vault(), + vault_root.vault_pubkey + ); + assert_eq!(vault_avs_slasher_operator_ticket.avs(), avs_root.avs_pubkey); + assert_eq!( + vault_avs_slasher_operator_ticket.slasher(), + slasher.pubkey() + ); + assert_eq!( + vault_avs_slasher_operator_ticket.operator(), + operator_root.operator_pubkey + ); + } } diff --git a/vault_core/src/lib.rs b/vault_core/src/lib.rs index 62865688..8a0f2035 100644 --- a/vault_core/src/lib.rs +++ b/vault_core/src/lib.rs @@ -22,5 +22,4 @@ enum AccountType { VaultDelegationList, VaultAvsSlasherOperatorTicket, VaultStakerWithdrawalTicket, - VaultStakerWithdrawalTicketEmpty, } diff --git a/vault_core/src/vault_staker_withdrawal_ticket.rs b/vault_core/src/vault_staker_withdrawal_ticket.rs index f0a50f57..a4262c64 100644 --- a/vault_core/src/vault_staker_withdrawal_ticket.rs +++ b/vault_core/src/vault_staker_withdrawal_ticket.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -91,11 +91,16 @@ impl VaultStakerWithdrawalTicket { pub fn check_withdrawable(&self, slot: u64, epoch_length: u64) -> VaultCoreResult<()> { let current_epoch = slot.checked_div(epoch_length).unwrap(); // epoch_length is always > 0 let epoch_unstaked = self.slot_unstaked.checked_div(epoch_length).unwrap(); - if epoch_unstaked - .checked_add(1) - .ok_or(VaultCoreError::VaultStakerWithdrawalTicketOverflow)? - < current_epoch + if current_epoch + <= epoch_unstaked + .checked_add(1) + .ok_or(VaultCoreError::VaultStakerWithdrawalTicketOverflow)? { + msg!( + "current_epoch: {:?}, epoch_unstaked: {:?}", + current_epoch, + epoch_unstaked + ); return Err(VaultCoreError::VaultStakerWithdrawalTicketNotWithdrawable); } Ok(()) @@ -139,9 +144,7 @@ impl VaultStakerWithdrawalTicket { Self::deserialize(&mut account.data.borrow_mut().as_ref()).map_err(|e| { VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidData(e.to_string()) })?; - if vault_staker_withdrawal_ticket.account_type - != AccountType::VaultStakerWithdrawalTicketEmpty - { + if vault_staker_withdrawal_ticket.account_type != AccountType::VaultStakerWithdrawalTicket { return Err(VaultCoreError::VaultStakerWithdrawalTicketEmptyInvalidAccountType); } diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index adaf402c..a6eac967 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -22,7 +22,7 @@ use solana_program::{ }; use spl_token::instruction::{burn, close_account, transfer}; -/// Burns the withdrawal ticket, transferring the assets to the staker and closing the withdraw ticket. +/// Burns the withdrawal ticket, transferring the assets to the staker and closing the withdrawal ticket. /// /// One should call the [`crate::VaultInstruction::UpdateVault`] instruction before running this instruction /// to ensure that any rewards that were accrued are accounted for. @@ -51,6 +51,7 @@ pub fn process_burn_withdrawal_ticket( ProgramError::InvalidArgument, "LRT mint mismatch", )?; + vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .check_withdrawable(slot, epoch_length)?; @@ -61,6 +62,7 @@ pub fn process_burn_withdrawal_ticket( .vault_staker_withdrawal_ticket() .lrt_amount(), )?; + let original_redemption_amount = vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .withdraw_allocation_amount(); @@ -83,8 +85,7 @@ pub fn process_burn_withdrawal_ticket( let available_unstaked_assets = tokens_deposited_in_vault .checked_sub(delegated_security_in_vault) - .ok_or(ProgramError::InsufficientFunds)? - .checked_sub(assets_reserved_for_withdrawal_tickets) + .and_then(|x| x.checked_sub(assets_reserved_for_withdrawal_tickets)) .ok_or(ProgramError::InsufficientFunds)?; // Calculate the extra amount that can be withdrawn @@ -151,6 +152,7 @@ pub fn process_burn_withdrawal_ticket( vault_staker_withdrawal_ticket.account(), staker.account(), )?; + _close_token_account( program_id, &vault, @@ -183,33 +185,39 @@ fn _close_token_account<'a, 'info>( seeds.push(vec![bump]); let seed_slices: Vec<&[u8]> = seeds.iter().map(|seed| seed.as_slice()).collect(); - invoke_signed( - &transfer( - &spl_token::id(), - vault_staker_withdrawal_ticket_token_account.account().key, - staker_lrt_token_account.account().key, - vault.account().key, - &[], - vault_staker_withdrawal_ticket_token_account - .token_account() - .amount, - )?, - &[ - vault_staker_withdrawal_ticket_token_account - .account() - .clone(), - staker_lrt_token_account.account().clone(), - vault_staker_withdrawal_ticket.account().clone(), - ], - &[&seed_slices], - )?; + if vault_staker_withdrawal_ticket_token_account + .token_account() + .amount + > 0 + { + invoke_signed( + &transfer( + &spl_token::id(), + vault_staker_withdrawal_ticket_token_account.account().key, + staker_lrt_token_account.account().key, + vault_staker_withdrawal_ticket.account().key, + &[], + vault_staker_withdrawal_ticket_token_account + .token_account() + .amount, + )?, + &[ + vault_staker_withdrawal_ticket_token_account + .account() + .clone(), + staker_lrt_token_account.account().clone(), + vault_staker_withdrawal_ticket.account().clone(), + ], + &[&seed_slices], + )?; + } invoke_signed( &close_account( &spl_token::id(), vault_staker_withdrawal_ticket_token_account.account().key, staker.account().key, - staker.account().key, + vault_staker_withdrawal_ticket.account().key, &[], )?, &[ @@ -217,7 +225,7 @@ fn _close_token_account<'a, 'info>( .account() .clone(), staker.account().clone(), - staker.account().clone(), + vault_staker_withdrawal_ticket.account().clone(), ], &[&seed_slices], )?; @@ -279,7 +287,7 @@ fn _burn_lrt<'a, 'info>( &spl_token::id(), vault_staker_withdrawal_ticket_token_account.account().key, token_mint.account().key, - vault.account().key, + vault_staker_withdrawal_ticket.account().key, &[], burn_amount, )?, @@ -316,6 +324,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, @@ -350,7 +359,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let vault_staker_withdrawal_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, - &vault.vault().supported_mint(), + &vault.vault().lrt_mint(), vault_staker_withdrawal_ticket.account().key, )?; let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; diff --git a/vault_program/src/slash.rs b/vault_program/src/slash.rs index af9b5407..dabea1e6 100644 --- a/vault_program/src/slash.rs +++ b/vault_program/src/slash.rs @@ -22,7 +22,6 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, - msg, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, @@ -181,25 +180,20 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; - msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; - msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; - msg!("c"); let operator = SanitizedOperator::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; - msg!("d"); let slasher = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; - msg!("e"); let avs_operator_ticket = SanitizedAvsOperatorTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -207,7 +201,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, operator.account().key, )?; - msg!("f"); let operator_avs_ticket = SanitizedOperatorAvsTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -215,7 +208,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, avs.account().key, )?; - msg!("g"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -223,7 +215,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; - msg!("h"); let operator_vault_ticket = SanitizedOperatorVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -231,7 +222,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, vault.account().key, )?; - msg!("i"); let vault_avs_ticket = SanitizedVaultAvsTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -239,7 +229,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, avs.account().key, )?; - msg!("j"); let vault_operator_ticket = SanitizedVaultOperatorTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -247,7 +236,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, operator.account().key, )?; - msg!("k"); let avs_vault_slasher_ticket = SanitizedAvsVaultSlasherTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -256,7 +244,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, slasher.account().key, )?; - msg!("l"); let vault_avs_slasher_ticket = SanitizedVaultAvsSlasherTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -265,14 +252,12 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, slasher.account().key, )?; - msg!("m"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(&mut accounts_iter)?, true, vault.account().key, )?; - msg!("n"); let epoch = slot.checked_div(config.config().epoch_length()).unwrap(); let vault_avs_slasher_operator_ticket = SanitizedVaultAvsSlasherOperatorTicket::sanitize( program_id, @@ -284,20 +269,17 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, epoch, )?; - msg!("o"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; - msg!("p"); let slasher_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), slasher.account().key, )?; - msg!("q"); let _token_program = SanitizedTokenProgram::sanitize(next_account_info(&mut accounts_iter)?)?; Ok(Self { diff --git a/vault_sdk/src/lib.rs b/vault_sdk/src/lib.rs index b95daddd..6247a4e9 100644 --- a/vault_sdk/src/lib.rs +++ b/vault_sdk/src/lib.rs @@ -117,10 +117,11 @@ pub enum VaultInstruction { #[account(4, writable, name = "lrt_mint")] #[account(5, writable, signer, name = "staker")] #[account(6, writable, name = "staker_token_account")] - #[account(7, writable, name = "vault_staker_withdrawal_ticket")] - #[account(8, writable, name = "vault_staker_withdrawal_ticket_token_account")] - #[account(9, name = "token_program")] - #[account(10, name = "system_program")] + #[account(7, writable, name = "staker_lrt_token_account")] + #[account(8, writable, name = "vault_staker_withdrawal_ticket")] + #[account(9, writable, name = "vault_staker_withdrawal_ticket_token_account")] + #[account(10, name = "token_program")] + #[account(11, name = "system_program")] BurnWithdrawTicket, /// Sets the max tokens that can be deposited into the LRT @@ -763,3 +764,38 @@ pub fn enqueue_withdraw( .unwrap(), } } + +#[allow(clippy::too_many_arguments)] +pub fn burn_withdrawal_ticket( + program_id: &Pubkey, + config: &Pubkey, + vault: &Pubkey, + vault_delegation_list: &Pubkey, + vault_token_account: &Pubkey, + lrt_mint: &Pubkey, + staker: &Pubkey, + staker_token_account: &Pubkey, + staker_lrt_token_account: &Pubkey, + vault_staker_withdrawal_ticket: &Pubkey, + vault_staker_withdrawal_ticket_token_account: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new_readonly(*config, false), + AccountMeta::new(*vault, false), + AccountMeta::new(*vault_delegation_list, false), + AccountMeta::new(*vault_token_account, false), + AccountMeta::new(*lrt_mint, false), + AccountMeta::new(*staker, true), + AccountMeta::new(*staker_token_account, false), + AccountMeta::new(*staker_lrt_token_account, false), + AccountMeta::new(*vault_staker_withdrawal_ticket, false), + AccountMeta::new(*vault_staker_withdrawal_ticket_token_account, false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::BurnWithdrawTicket.try_to_vec().unwrap(), + } +} From 588fe11c4a0acf67b96431edbcb8d1baaba19f79 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Wed, 7 Aug 2024 21:01:22 -0500 Subject: [PATCH 17/22] werd --- .../tests/fixtures/vault_client.rs | 20 +- .../tests/vault/burn_withdrawal_ticket.rs | 425 +++++++++++++++++- vault_core/src/operator_delegation.rs | 4 +- vault_core/src/vault_delegation_list.rs | 4 + vault_program/src/burn_withdrawal_ticket.rs | 47 +- vault_program/src/slash.rs | 33 +- 6 files changed, 499 insertions(+), 34 deletions(-) diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 18a3b98a..73e5627b 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -13,6 +13,7 @@ use jito_vault_core::{ use jito_vault_sdk::{add_delegation, initialize_config, initialize_vault}; use log::info; use solana_program::{ + clock::Clock, native_token::sol_to_lamports, program_pack::Pack, pubkey::Pubkey, @@ -278,6 +279,12 @@ impl VaultProgramClient { slasher: &Pubkey, operator_pubkey: &Pubkey, ) -> Result<(), BanksClientError> { + let config = self + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + let clock: Clock = self.banks_client.get_sysvar().await?; + let vault_avs_slasher_ticket = VaultAvsSlasherTicket::find_program_address( &jito_vault_program::id(), &vault_root.vault_pubkey, @@ -292,7 +299,7 @@ impl VaultProgramClient { avs_pubkey, slasher, operator_pubkey, - 0, // TODO (LB): fix this + clock.slot / config.epoch_length(), ) .0; self.initialize_vault_avs_slasher_operator_ticket( @@ -374,6 +381,12 @@ impl VaultProgramClient { &vault_root.vault_pubkey, ) .0; + let config = self + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + let clock: Clock = self.banks_client.get_sysvar().await?; + let vault_avs_slasher_operator_ticket = VaultAvsSlasherOperatorTicket::find_program_address( &jito_vault_program::id(), @@ -381,7 +394,7 @@ impl VaultProgramClient { avs_pubkey, &slasher.pubkey(), operator_pubkey, - 0, // TODO (LB): fix this + clock.slot / config.epoch_length(), ) .0; @@ -411,8 +424,7 @@ impl VaultProgramClient { &slasher_token_account, amount, ) - .await - .unwrap(); + .await?; Ok(()) } diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs index 58d9a765..e9abba5f 100644 --- a/integration_tests/tests/vault/burn_withdrawal_ticket.rs +++ b/integration_tests/tests/vault/burn_withdrawal_ticket.rs @@ -12,7 +12,6 @@ mod tests { }; struct PreparedWithdrawalTicket { - vault_config_admin: Keypair, vault_root: VaultRoot, avs_root: AvsRoot, operator_root: OperatorRoot, @@ -32,11 +31,11 @@ mod tests { withdrawal_amount: u64, ) -> PreparedWithdrawalTicket { // Setup vault with initial deposit - let (vault_config_admin, vault_root) = vault_program_client + let (_vault_config_admin, vault_root) = vault_program_client .setup_vault(deposit_fee_bps, withdraw_fee_bps) .await .unwrap(); - let restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); + let _restaking_config_admin = restaking_program_client.setup_config().await.unwrap(); // Setup operator and AVS let operator_root = restaking_program_client.setup_operator().await.unwrap(); @@ -113,7 +112,6 @@ mod tests { .unwrap(); PreparedWithdrawalTicket { - vault_config_admin, vault_root, avs_root, operator_root, @@ -129,10 +127,9 @@ mod tests { let mut restaking_program_client = fixture.restaking_program_client(); let PreparedWithdrawalTicket { - vault_config_admin, vault_root, - avs_root, - operator_root, + avs_root: _, + operator_root: _, depositor, withdrawal_ticket_base, } = setup_withdrawal_ticket( @@ -162,10 +159,9 @@ mod tests { let mut restaking_program_client = fixture.restaking_program_client(); let PreparedWithdrawalTicket { - vault_config_admin, vault_root, - avs_root, - operator_root, + avs_root: _, + operator_root: _, depositor, withdrawal_ticket_base, } = setup_withdrawal_ticket( @@ -209,10 +205,9 @@ mod tests { let mut restaking_program_client = fixture.restaking_program_client(); let PreparedWithdrawalTicket { - vault_config_admin, vault_root, - avs_root, - operator_root, + avs_root: _, + operator_root: _, depositor, withdrawal_ticket_base, } = setup_withdrawal_ticket( @@ -222,9 +217,9 @@ mod tests { 0, 0, 1000, - 100, - 100, - 100, + 1000, + 1000, + 1000, ) .await; @@ -247,14 +242,406 @@ mod tests { .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) .await .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + let depositor_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_token_account.amount, 1000); } #[tokio::test] - async fn test_burn_withdrawal_ticket_with_unstaked_rewards() {} + async fn test_burn_withdrawal_ticket_with_unstaked_rewards() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_root, + avs_root: _, + operator_root: _, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 1000, + 1000, + 1000, + ) + .await; + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // send 100 tokens to vault as rewards, increasing value of it by 10% + fixture + .mint_to(&vault.supported_mint(), &vault_root.vault_pubkey, 100) + .await + .unwrap(); + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); + + // user should have 1100 tokens + let depositor_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_token_account.amount, 1100); + } #[tokio::test] - async fn test_burn_withdrawal_ticket_with_staked_rewards() {} + async fn test_burn_withdrawal_ticket_with_staked_rewards() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_root, + avs_root: _, + operator_root, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 1000, + 1000, + 1000, + ) + .await; + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // send 100 tokens to vault as rewards, increasing value of it by 10% + // but delegate those to the operator. they won't be available for withdraw + fixture + .mint_to(&vault.supported_mint(), &vault_root.vault_pubkey, 100) + .await + .unwrap(); + + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + vault_program_client + .delegate(&vault_root, &operator_root.operator_pubkey, 100) + .await + .unwrap(); + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); + + // user should have 1000 tokens and should also get back excess LRT tokens + let depositor_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_token_account.amount, 1000); + + let depositor_lrt_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_lrt_token_account.amount, 91); + + let vault_token_account = fixture + .get_token_account(&get_associated_token_address( + &vault_root.vault_pubkey, + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_token_account.amount, 100); + } + + // #[tokio::test] + // async fn test_burn_withdrawal_ticket_with_slashing_before_update() { + // let mut fixture = TestBuilder::new().await; + // let mut vault_program_client = fixture.vault_program_client(); + // let mut restaking_program_client = fixture.restaking_program_client(); + // + // let PreparedWithdrawalTicket { + // vault_config_admin, + // vault_root, + // avs_root, + // operator_root, + // depositor, + // withdrawal_ticket_base, + // } = setup_withdrawal_ticket( + // &mut fixture, + // &mut vault_program_client, + // &mut restaking_program_client, + // 0, + // 0, + // 1000, + // 1000, + // 1000, + // 1000, + // ) + // .await; + // + // let vault = vault_program_client + // .get_vault(&vault_root.vault_pubkey) + // .await + // .unwrap(); + // + // // create slasher w/ token account + // let slasher = Keypair::new(); + // fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); + // fixture + // .create_ata(&vault.supported_mint(), &slasher.pubkey()) + // .await + // .unwrap(); + // + // // do all the opt-in stuff + // restaking_program_client + // .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) + // .await + // .unwrap(); + // vault_program_client + // .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) + // .await + // .unwrap(); + // + // vault_program_client + // .setup_vault_avs_slasher_operator_ticket( + // &vault_root, + // &avs_root.avs_pubkey, + // &slasher.pubkey(), + // &operator_root.operator_pubkey, + // ) + // .await + // .unwrap(); + // vault_program_client + // .do_slash( + // &vault_root, + // &avs_root.avs_pubkey, + // &slasher, + // &operator_root.operator_pubkey, + // 100, + // ) + // .await + // .unwrap(); + // + // let config = vault_program_client + // .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + // .await + // .unwrap(); + // fixture + // .warp_slot_incremental(2 * config.epoch_length()) + // .await + // .unwrap(); + // + // vault_program_client + // .do_update_vault(&vault_root.vault_pubkey) + // .await + // .unwrap(); + // + // vault_program_client + // .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + // .await + // .unwrap(); + // } #[tokio::test] - async fn test_burn_withdrawal_ticket_with_slashing() {} + async fn test_burn_withdrawal_ticket_with_slashing_after_update() { + let mut fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + let mut restaking_program_client = fixture.restaking_program_client(); + + let PreparedWithdrawalTicket { + vault_root, + avs_root, + operator_root, + depositor, + withdrawal_ticket_base, + } = setup_withdrawal_ticket( + &mut fixture, + &mut vault_program_client, + &mut restaking_program_client, + 0, + 0, + 1000, + 1000, + 1000, + 900, + ) + .await; + + let vault = vault_program_client + .get_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + // create slasher w/ token account + let slasher = Keypair::new(); + fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); + fixture + .create_ata(&vault.supported_mint(), &slasher.pubkey()) + .await + .unwrap(); + + // do all the opt-in stuff + restaking_program_client + .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) + .await + .unwrap(); + vault_program_client + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) + .await + .unwrap(); + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + + vault_program_client + .setup_vault_avs_slasher_operator_ticket( + &vault_root, + &avs_root.avs_pubkey, + &slasher.pubkey(), + &operator_root.operator_pubkey, + ) + .await + .unwrap(); + vault_program_client + .do_slash( + &vault_root, + &avs_root.avs_pubkey, + &slasher, + &operator_root.operator_pubkey, + 100, + ) + .await + .unwrap(); + + vault_program_client + .do_burn_withdrawal_ticket(&vault_root, &depositor, &withdrawal_ticket_base) + .await + .unwrap(); + + let vault_delegation_list = vault_program_client + .get_vault_delegation_list(&vault_root.vault_pubkey) + .await + .unwrap(); + assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); + + let depositor_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_token_account.amount, 810); + + let depositor_lrt_token_account = fixture + .get_token_account(&get_associated_token_address( + &depositor.pubkey(), + &vault.lrt_mint(), + )) + .await + .unwrap(); + assert_eq!(depositor_lrt_token_account.amount, 100); + + let vault_token_account = fixture + .get_token_account(&get_associated_token_address( + &vault_root.vault_pubkey, + &vault.supported_mint(), + )) + .await + .unwrap(); + assert_eq!(vault_token_account.amount, 90); + } } diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 1e380403..64502be0 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -1,7 +1,7 @@ use std::cmp::min; use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::pubkey::Pubkey; +use solana_program::{msg, pubkey::Pubkey}; use crate::result::{VaultCoreError, VaultCoreResult}; @@ -113,6 +113,7 @@ impl OperatorDelegation { pub fn slash(&mut self, slash_amount: u64) -> VaultCoreResult<()> { let total_security_amount = self.total_security()?; if slash_amount > total_security_amount { + msg!("slashing too much"); return Err(VaultCoreError::VaultSlashingUnderflow); } @@ -147,6 +148,7 @@ impl OperatorDelegation { // Ensure we've slashed the exact amount requested if remaining_slash > 0 { + msg!("slashing incomplete"); return Err(VaultCoreError::VaultSlashingIncomplete); } diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 6b64493a..f88327fd 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -117,6 +117,7 @@ impl VaultDelegationList { #[inline(always)] pub fn check_update_needed(&self, slot: u64, epoch_length: u64) -> VaultCoreResult<()> { if self.is_update_needed(slot, epoch_length) { + msg!("update needed bro"); Err(VaultCoreError::VaultDelegationListUpdateRequired) } else { Ok(()) @@ -157,6 +158,9 @@ impl VaultDelegationList { } } } + + self.last_slot_updated = slot; + Ok(true) } diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index a6eac967..20ce1b0c 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -15,6 +15,7 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, + msg, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, @@ -46,15 +47,21 @@ pub fn process_burn_withdrawal_ticket( let slot = Clock::get()?.slot; let epoch_length = config.config().epoch_length(); + vault_delegation_list + .vault_delegation_list() + .check_update_needed(slot, epoch_length)?; + assert_with_msg( vault.vault().lrt_mint() == *lrt_mint.account().key, ProgramError::InvalidArgument, "LRT mint mismatch", )?; + msg!("1"); vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .check_withdrawable(slot, epoch_length)?; + msg!("2"); // find the current redemption amount and the original redemption amount in the withdraw ticket let redemption_amount = vault.vault().calculate_assets_returned_amount( @@ -62,10 +69,12 @@ pub fn process_burn_withdrawal_ticket( .vault_staker_withdrawal_ticket() .lrt_amount(), )?; + msg!("3"); let original_redemption_amount = vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .withdraw_allocation_amount(); + msg!("4"); let actual_withdraw_amount = if redemption_amount > original_redemption_amount { // The program can guarantee the original redemption amount, but if the redemption amount @@ -100,16 +109,19 @@ pub fn process_burn_withdrawal_ticket( } else { redemption_amount }; + msg!("5"); let lrt_to_burn = vault .vault() .calculate_lrt_mint_amount(actual_withdraw_amount)?; + msg!("6"); let lrt_amount_to_burn = std::cmp::min( lrt_to_burn, vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .lrt_amount(), ); + msg!("7"); _burn_lrt( program_id, @@ -120,9 +132,11 @@ pub fn process_burn_withdrawal_ticket( &lrt_mint, lrt_amount_to_burn, )?; + msg!("8"); lrt_mint.reload()?; vault_staker_withdrawal_ticket_token_account.reload()?; + msg!("9"); _transfer_vault_tokens_to_staker( program_id, &vault, @@ -133,11 +147,20 @@ pub fn process_burn_withdrawal_ticket( vault_token_account.reload()?; staker_token_account.reload()?; + msg!( + "decrementing reserve amount: {:?}, amount available: {:?}", + original_redemption_amount, + vault_delegation_list + .vault_delegation_list() + .withdrawable_reserve_amount() + ); // Decrement the amount reserved for withdraw tickets because it's been claimed now + // TODO (LB): how to handle slashing? look at change in ratio? vault_delegation_list .vault_delegation_list_mut() .decrement_withdrawable_reserve_amount(original_redemption_amount)?; + msg!("set tokens deposited"); // refresh after burn vault .vault_mut() @@ -147,12 +170,7 @@ pub fn process_burn_withdrawal_ticket( vault.save()?; vault_delegation_list.save()?; - close_program_account( - program_id, - vault_staker_withdrawal_ticket.account(), - staker.account(), - )?; - + msg!("close token account"); _close_token_account( program_id, &vault, @@ -162,6 +180,13 @@ pub fn process_burn_withdrawal_ticket( &staker_lrt_token_account, )?; + msg!("close program account"); + close_program_account( + program_id, + vault_staker_withdrawal_ticket.account(), + staker.account(), + )?; + Ok(()) } @@ -325,30 +350,38 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; + msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; + msg!("b"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(accounts_iter)?, true, vault.account().key, )?; + msg!("c"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; + msg!("d"); let lrt_mint = SanitizedTokenMint::sanitize(next_account_info(accounts_iter)?, true)?; + msg!("e"); let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; + msg!("f"); let staker_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().supported_mint(), staker.account().key, )?; + msg!("g"); let staker_lrt_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), staker.account().key, )?; + msg!("h"); let vault_staker_withdrawal_ticket = SanitizedVaultStakerWithdrawalTicket::sanitize( program_id, next_account_info(accounts_iter)?, @@ -356,12 +389,14 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { staker.account().key, true, )?; + msg!("i"); let vault_staker_withdrawal_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), vault_staker_withdrawal_ticket.account().key, )?; + msg!("j"); let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; let _system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; diff --git a/vault_program/src/slash.rs b/vault_program/src/slash.rs index dabea1e6..1559783c 100644 --- a/vault_program/src/slash.rs +++ b/vault_program/src/slash.rs @@ -22,6 +22,7 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, + msg, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, @@ -54,11 +55,18 @@ pub fn process_slash( slasher_token_account, } = SanitizedAccounts::sanitize(program_id, accounts, slot)?; + msg!("checking update needed"); + vault_delegation_list + .vault_delegation_list_mut() + .check_update_needed(slot, config.config().epoch_length())?; + // The vault shall be opted-in to the AVS and the AVS shall be opted-in to the vault + msg!("vault <> avs check"); vault_avs_ticket.vault_avs_ticket().check_active(slot)?; avs_vault_ticket.avs_vault_ticket().check_active(slot)?; // The operator shall be opted-in to vault and the vault shall be staked to the operator + msg!("operator <> vault check"); operator_vault_ticket .operator_vault_ticket() .check_active(slot)?; @@ -67,6 +75,7 @@ pub fn process_slash( .check_active(slot)?; // The operator shall be opted-in to the AVS and the AVS shall be opted-in to the operator + msg!("avs <> operator check"); avs_operator_ticket .avs_operator_ticket() .check_active(slot)?; @@ -74,6 +83,7 @@ pub fn process_slash( .operator_avs_ticket() .check_active(slot)?; // The slasher shall be active for the AVS and the vault + msg!("avs <> vault check"); avs_vault_slasher_ticket .avs_vault_slasher_ticket() .check_active(slot)?; @@ -81,6 +91,7 @@ pub fn process_slash( .vault_avs_slasher_ticket() .check_active(slot)?; + msg!("max exceeded check"); let max_slashable_per_epoch = vault_avs_slasher_ticket .vault_avs_slasher_ticket() .max_slashable_per_epoch(); @@ -88,16 +99,14 @@ pub fn process_slash( .vault_avs_slasher_operator_ticket() .check_max_slashable_not_exceeded(slash_amount, max_slashable_per_epoch)?; - vault_delegation_list - .vault_delegation_list_mut() - .check_update_needed(slot, config.config().epoch_length())?; - + msg!("slashing"); vault_delegation_list .vault_delegation_list_mut() .slash(operator.account().key, slash_amount)?; vault_avs_slasher_operator_ticket .vault_avs_slasher_operator_ticket_mut() .increment_slashed_amount(slash_amount)?; + msg!("sending slashed funds"); _transfer_slashed_funds( &vault, &vault_token_account, @@ -180,20 +189,25 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; + msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; + msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; + msg!("c"); let operator = SanitizedOperator::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; + msg!("d"); let slasher = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; + msg!("e"); let avs_operator_ticket = SanitizedAvsOperatorTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -201,6 +215,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, operator.account().key, )?; + msg!("f"); let operator_avs_ticket = SanitizedOperatorAvsTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -208,6 +223,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, avs.account().key, )?; + msg!("g"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -215,6 +231,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; + msg!("h"); let operator_vault_ticket = SanitizedOperatorVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -222,6 +239,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, vault.account().key, )?; + msg!("i"); let vault_avs_ticket = SanitizedVaultAvsTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -229,6 +247,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, avs.account().key, )?; + msg!("j"); let vault_operator_ticket = SanitizedVaultOperatorTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -236,6 +255,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, operator.account().key, )?; + msg!("k"); let avs_vault_slasher_ticket = SanitizedAvsVaultSlasherTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -244,6 +264,7 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, slasher.account().key, )?; + msg!("l"); let vault_avs_slasher_ticket = SanitizedVaultAvsSlasherTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -252,12 +273,14 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, slasher.account().key, )?; + msg!("m"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(&mut accounts_iter)?, true, vault.account().key, )?; + msg!("n"); let epoch = slot.checked_div(config.config().epoch_length()).unwrap(); let vault_avs_slasher_operator_ticket = SanitizedVaultAvsSlasherOperatorTicket::sanitize( program_id, @@ -270,11 +293,13 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { epoch, )?; + msg!("o"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; + msg!("p"); let slasher_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), From 24d5d87bb8919901dd801141ea86ebdb87113ed4 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Thu, 8 Aug 2024 15:26:18 -0500 Subject: [PATCH 18/22] fixes --- .../tests/vault/burn_withdrawal_ticket.rs | 22 ++- vault_core/src/operator_delegation.rs | 3 +- vault_core/src/vault_delegation_list.rs | 173 +++++++++++++++++- vault_program/src/burn_withdrawal_ticket.rs | 19 +- 4 files changed, 198 insertions(+), 19 deletions(-) diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs index e9abba5f..b7080c81 100644 --- a/integration_tests/tests/vault/burn_withdrawal_ticket.rs +++ b/integration_tests/tests/vault/burn_withdrawal_ticket.rs @@ -120,6 +120,7 @@ mod tests { } } + /// One can't burn the withdraw ticket until a full epoch has passed #[tokio::test] async fn test_burn_withdrawal_ticket_same_epoch_fails() { let mut fixture = TestBuilder::new().await; @@ -152,6 +153,7 @@ mod tests { .unwrap_err(); } + /// One can't burn the withdraw ticket until a full epoch has passed #[tokio::test] async fn test_burn_withdrawal_ticket_next_epoch_fails() { let mut fixture = TestBuilder::new().await; @@ -198,6 +200,7 @@ mod tests { .unwrap_err(); } + /// Tests basic withdraw ticket with no rewards or slashing incidents #[tokio::test] async fn test_burn_withdrawal_ticket_basic_success() { let mut fixture = TestBuilder::new().await; @@ -264,6 +267,9 @@ mod tests { assert_eq!(depositor_token_account.amount, 1000); } + /// The user withdrew at some ratio of the vault, but rewards were accrued so the amount of + /// assets the user gets back shall be larger than the amount set aside for withdrawal. + /// The rewards were not staked, so they can be fully withdrawn from the vault. #[tokio::test] async fn test_burn_withdrawal_ticket_with_unstaked_rewards() { let mut fixture = TestBuilder::new().await; @@ -336,6 +342,10 @@ mod tests { assert_eq!(depositor_token_account.amount, 1100); } + /// The user withdrew at some ratio of the vault, but rewards were accrued so the amount of + /// assets the user gets back shall be larger than the amount set aside for withdrawal. However, + /// those rewards were staked, so the user can't receive them. In this case, they shall receive + /// back the amount set aside for withdraw and the excess LRT tokens. #[tokio::test] async fn test_burn_withdrawal_ticket_with_staked_rewards() { let mut fixture = TestBuilder::new().await; @@ -436,6 +446,14 @@ mod tests { assert_eq!(vault_token_account.amount, 100); } + // /// This test is complicated... TODO (LB): need to figure out this logic guh + // /// + // /// The user withdrew at some ratio of the vault, but a slashing took place while the withdrawal ticket + // /// was maturing. The user gets back less than they originally anticipated and the amount of withdrawal + // /// set aside is reduced to 0. + // /// + // /// This test is more complicated because the withdrawal amount reserved stored in the vault delegation list + // /// won't match the withdrawal amount reserved in the withdrawal ticket. // #[tokio::test] // async fn test_burn_withdrawal_ticket_with_slashing_before_update() { // let mut fixture = TestBuilder::new().await; @@ -443,7 +461,6 @@ mod tests { // let mut restaking_program_client = fixture.restaking_program_client(); // // let PreparedWithdrawalTicket { - // vault_config_admin, // vault_root, // avs_root, // operator_root, @@ -525,6 +542,9 @@ mod tests { // .unwrap(); // } + /// The user withdrew at some ratio of the vault, but a slashing took place after the withdrawal ticket + /// had matured. The user gets back less than they originally anticipated and the amount of withdrawal + /// set aside is reduced to 0. #[tokio::test] async fn test_burn_withdrawal_ticket_with_slashing_after_update() { let mut fixture = TestBuilder::new().await; diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 64502be0..1d67627d 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -127,8 +127,7 @@ impl OperatorDelegation { let pro_rata_slash = (*amount as u128) .checked_mul(slash_amount as u128) .ok_or(VaultCoreError::VaultSlashingOverflow)? - .checked_div(total_security_amount as u128) - .ok_or(VaultCoreError::VaultSlashingDivisionByZero)?; + .div_ceil(total_security_amount as u128); let actual_slash = min(pro_rata_slash as u64, min(*amount, remaining_slash)); *amount = amount .checked_sub(actual_slash) diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index f88327fd..89b1fcad 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -80,7 +80,7 @@ impl VaultDelegationList { } /// Returns the total security in the delegation list - pub fn all_security(&self) -> VaultCoreResult { + pub fn total_security(&self) -> VaultCoreResult { let mut total: u64 = 0; for operator in self.delegations.iter() { total = total @@ -180,7 +180,7 @@ impl VaultDelegationList { amount: u64, total_deposited: u64, ) -> VaultCoreResult<()> { - let delegated_security = self.all_security()?; + let delegated_security = self.total_security()?; // Ensure the amount delegated doesn't exceed the total deposited let security_available_for_delegation = total_deposited @@ -407,7 +407,7 @@ mod tests { let operator = Pubkey::new_unique(); assert!(list.delegate(operator, 100, 1_000).is_ok()); - assert_eq!(list.all_security().unwrap(), 100); + assert_eq!(list.total_security().unwrap(), 100); assert_eq!(list.delegations().len(), 1); let delegation = list.delegations().get(0).unwrap(); @@ -422,7 +422,7 @@ mod tests { let operator = Pubkey::new_unique(); list.delegate(operator, 100, 1_000).unwrap(); list.delegate(operator, 50, 1_000).unwrap(); - assert_eq!(list.all_security().unwrap(), 150); + assert_eq!(list.total_security().unwrap(), 150); assert_eq!(list.delegations().len(), 1); let delegation = list.delegations().get(0).unwrap(); @@ -513,7 +513,7 @@ mod tests { assert!(list.update(200, 100).unwrap()); assert_eq!(list.withdrawable_reserve_amount(), undelegate_amount); assert_eq!( - list.all_security().unwrap(), + list.total_security().unwrap(), initial_delegation - undelegate_amount ); @@ -550,14 +550,14 @@ mod tests { list.delegate(operator2, 1500, 3000).unwrap(); list.delegate(operator3, 500, 3000).unwrap(); - let total_delegated_before_undelegation = list.all_security().unwrap(); + let total_delegated_before_undelegation = list.total_security().unwrap(); list.undelegate_for_withdrawal(600, UndelegateForWithdrawMethod::ProRata) .unwrap(); assert_eq!( total_delegated_before_undelegation, - list.all_security().unwrap() + list.total_security().unwrap() ); // 3000 total staked, 600 withdrawn @@ -645,7 +645,7 @@ mod tests { list.undelegate(operator_2, 30_000).unwrap(); - assert_eq!(list.all_security().unwrap(), total_deposited); + assert_eq!(list.total_security().unwrap(), total_deposited); let delegation_1 = list.delegations().get(0).unwrap(); assert_eq!(delegation_1.operator(), operator_1); @@ -757,4 +757,161 @@ mod tests { let delegation_2 = list.delegations().get(1).unwrap(); assert_eq!(delegation_2.enqueued_for_withdraw_amount(), 99_998); } + + #[test] + fn test_slash_with_enqueued_for_cooldown_down_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + list.undelegate(operator_1, 25_000).unwrap(); + + list.slash(&operator_1, 10_000).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + + // 100k staked, 25k enqueued + 10k slashed + // 25% of staked assets was enqueued for cooldown -> 25% of slashed funds -> 2500 + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.enqueued_for_cooldown_amount(), 22500); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_cooling_down_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + list.undelegate(operator_1, 25_000).unwrap(); + + list.update(100, 100).unwrap(); + + list.slash(&operator_1, 10_000).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + + // 100k staked, 25k cooldown + 10k slashed + // 25% of staked assets were cooling down -> 25% of slashed funds -> 2500 + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.cooling_down_amount(), 22500); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_enqueued_for_cooldown_and_cooling_down_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + + list.undelegate(operator_1, 12500).unwrap(); + list.update(100, 100).unwrap(); + list.undelegate(operator_1, 12500).unwrap(); + + list.slash(&operator_1, 10_000).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.cooling_down_amount(), 11250); + assert_eq!(delegation.enqueued_for_cooldown_amount(), 11250); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_enqueued_for_withdraw_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + list.undelegate_for_withdrawal(25_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + list.slash(&operator_1, 10_000).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 22500); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_cooling_down_for_withdraw_assets() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(25_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + list.slash(&operator_1, 10_000).unwrap(); + list.update(100, 100).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.cooling_down_for_withdraw_amount(), 22500); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_enqueued_for_withdraw_and_cooling_down_for_withdraw() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(12500, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + list.update(100, 100).unwrap(); + list.undelegate_for_withdrawal(12500, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + + list.slash(&operator_1, 10_000).unwrap(); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 67500); + assert_eq!(delegation.cooling_down_for_withdraw_amount(), 11250); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 11250); + assert_eq!(delegation.total_security().unwrap(), 90_000); + } + + #[test] + fn test_slash_with_withdraw_reserves() { + let mut list = setup_vault_delegation_list(); + let total_deposited = 100_000; + + let operator_1 = Pubkey::new_unique(); + list.delegate(operator_1, 100_000, total_deposited).unwrap(); + + list.undelegate_for_withdrawal(25_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + list.update(100, 100).unwrap(); + list.undelegate_for_withdrawal(25_000, UndelegateForWithdrawMethod::ProRata) + .unwrap(); + list.update(200, 100).unwrap(); + + assert_eq!(list.withdrawable_reserve_amount(), 25_000); + + let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 50000); + assert_eq!(delegation.cooling_down_for_withdraw_amount(), 25000); + assert_eq!(delegation.total_security().unwrap(), 75000); + + list.slash(&operator_1, 10_000).unwrap(); + let delegation = list.delegations().get(0).unwrap(); + + // 2/3 staked -> 2/3 of slashed + assert_eq!(delegation.staked_amount(), 43333); + assert_eq!(delegation.cooling_down_for_withdraw_amount(), 21667); + assert_eq!(delegation.total_security().unwrap(), 65000); + } } diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index 20ce1b0c..aa264727 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -87,7 +87,7 @@ pub fn process_burn_withdrawal_ticket( let tokens_deposited_in_vault = vault.vault().tokens_deposited(); let delegated_security_in_vault = vault_delegation_list .vault_delegation_list() - .all_security()?; + .total_security()?; let assets_reserved_for_withdrawal_tickets = vault_delegation_list .vault_delegation_list() .withdrawable_reserve_amount(); @@ -154,8 +154,13 @@ pub fn process_burn_withdrawal_ticket( .vault_delegation_list() .withdrawable_reserve_amount() ); - // Decrement the amount reserved for withdraw tickets because it's been claimed now - // TODO (LB): how to handle slashing? look at change in ratio? + + // Decrement the amount reserved for withdraw tickets because it's been claimed now. + // This part is tricky in the event the ratio + // TODO (LB): this is tricky... if there's a withdraw ticket and there's a slashing event in the epoch + // where withdrawal funds are cooling down, the slashing event will be applied to the withdraw reserve on the operator + // which propagates to this. if there's a slashing after the withdrawed funds are fully cooled down and ready, it won't + // show up in this. how do we reconcile this? vault_delegation_list .vault_delegation_list_mut() .decrement_withdrawable_reserve_amount(original_redemption_amount)?; @@ -167,10 +172,6 @@ pub fn process_burn_withdrawal_ticket( .set_tokens_deposited(vault_token_account.token_account().amount); vault.vault_mut().set_lrt_supply(lrt_mint.mint().supply); - vault.save()?; - vault_delegation_list.save()?; - - msg!("close token account"); _close_token_account( program_id, &vault, @@ -180,13 +181,15 @@ pub fn process_burn_withdrawal_ticket( &staker_lrt_token_account, )?; - msg!("close program account"); close_program_account( program_id, vault_staker_withdrawal_ticket.account(), staker.account(), )?; + vault.save()?; + vault_delegation_list.save()?; + Ok(()) } From c68690feec2f0e9db9269121e8910535314baa08 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Thu, 8 Aug 2024 23:10:43 -0500 Subject: [PATCH 19/22] werd --- .config/nextest.toml | 1 + .../tests/vault/burn_withdrawal_ticket.rs | 56 ++++----- restaking_core/src/avs_operator_ticket.rs | 3 +- .../src/avs_vault_slasher_ticket.rs | 3 +- restaking_core/src/avs_vault_ticket.rs | 3 +- restaking_core/src/operator_avs_ticket.rs | 3 +- restaking_core/src/operator_vault_ticket.rs | 3 +- vault_core/src/operator_delegation.rs | 8 +- vault_core/src/result.rs | 3 +- vault_core/src/vault.rs | 62 ++++++++-- .../src/vault_avs_slasher_operator_ticket.rs | 14 ++- vault_core/src/vault_avs_slasher_ticket.rs | 3 +- vault_core/src/vault_avs_ticket.rs | 3 +- vault_core/src/vault_delegation_list.rs | 108 +++++++++--------- vault_core/src/vault_operator_ticket.rs | 3 +- vault_program/src/add_delegation.rs | 2 +- vault_program/src/burn_withdrawal_ticket.rs | 35 +----- vault_program/src/slash.rs | 25 ---- vault_program/src/update_vault.rs | 16 ++- 19 files changed, 182 insertions(+), 172 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index af9c6a24..6475a804 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -3,3 +3,4 @@ retries = 0 test-threads = "num-cpus" threads-required = 1 fail-fast = true + diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs index 9548a15f..7b4df43a 100644 --- a/integration_tests/tests/vault/burn_withdrawal_ticket.rs +++ b/integration_tests/tests/vault/burn_withdrawal_ticket.rs @@ -276,12 +276,6 @@ mod tests { .await .unwrap(); - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); - let vault = vault_program_client .get_vault(&vault_root.vault_pubkey) .await @@ -355,12 +349,6 @@ mod tests { .await .unwrap(); - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); - // user should have 1100 tokens let depositor_token_account = fixture .get_token_account(&get_associated_token_address( @@ -441,12 +429,6 @@ mod tests { .await .unwrap(); - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); - // user should have 1000 tokens and should also get back excess LRT tokens let depositor_token_account = fixture .get_token_account(&get_associated_token_address( @@ -476,14 +458,14 @@ mod tests { assert_eq!(vault_token_account.amount, 100); } - // /// This test is complicated... TODO (LB): need to figure out this logic guh - // /// - // /// The user withdrew at some ratio of the vault, but a slashing took place while the withdrawal ticket - // /// was maturing. The user gets back less than they originally anticipated and the amount of withdrawal - // /// set aside is reduced to 0. - // /// - // /// This test is more complicated because the withdrawal amount reserved stored in the vault delegation list - // /// won't match the withdrawal amount reserved in the withdrawal ticket. + /// This test is complicated... TODO (LB): need to figure out this logic guh + /// + /// The user withdrew at some ratio of the vault, but a slashing took place while the withdrawal ticket + /// was maturing. The user gets back less than they originally anticipated and the amount of withdrawal + /// set aside is reduced to 0. + /// + /// This test is more complicated because the withdrawal amount reserved stored in the vault delegation list + /// won't match the withdrawal amount reserved in the withdrawal ticket. // #[tokio::test] // async fn test_burn_withdrawal_ticket_with_slashing_before_update() { // let mut fixture = TestBuilder::new().await; @@ -514,6 +496,11 @@ mod tests { // .await // .unwrap(); // + // let config = vault_program_client + // .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + // .await + // .unwrap(); + // // // create slasher w/ token account // let slasher = Keypair::new(); // fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); @@ -527,11 +514,22 @@ mod tests { // .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) // .await // .unwrap(); + // + // fixture + // .warp_slot_incremental(2 * config.epoch_length()) + // .await + // .unwrap(); + // // vault_program_client // .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) // .await // .unwrap(); // + // fixture + // .warp_slot_incremental(2 * config.epoch_length()) + // .await + // .unwrap(); + // // vault_program_client // .setup_vault_avs_slasher_operator_ticket( // &vault_root, @@ -670,12 +668,6 @@ mod tests { .await .unwrap(); - let vault_delegation_list = vault_program_client - .get_vault_delegation_list(&vault_root.vault_pubkey) - .await - .unwrap(); - assert_eq!(vault_delegation_list.withdrawable_reserve_amount(), 0); - let depositor_token_account = fixture .get_token_account(&get_associated_token_address( &depositor.pubkey(), diff --git a/restaking_core/src/avs_operator_ticket.rs b/restaking_core/src/avs_operator_ticket.rs index d33c6f5f..94c61ed4 100644 --- a/restaking_core/src/avs_operator_ticket.rs +++ b/restaking_core/src/avs_operator_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{RestakingCoreError, RestakingCoreResult}, @@ -69,6 +69,7 @@ impl AvsOperatorTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("AvsOperatorTicket is not active or in cooldown"); Err(RestakingCoreError::AvsOperatorTicketInactive) } } diff --git a/restaking_core/src/avs_vault_slasher_ticket.rs b/restaking_core/src/avs_vault_slasher_ticket.rs index e465ec1e..cadb3afd 100644 --- a/restaking_core/src/avs_vault_slasher_ticket.rs +++ b/restaking_core/src/avs_vault_slasher_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{RestakingCoreError, RestakingCoreResult}, @@ -96,6 +96,7 @@ impl AvsVaultSlasherTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("AvsVaultSlasherTicket is not active or in cooldown"); Err(RestakingCoreError::AvsVaultSlasherTicketInactive) } } diff --git a/restaking_core/src/avs_vault_ticket.rs b/restaking_core/src/avs_vault_ticket.rs index 82cfcb61..2505016c 100644 --- a/restaking_core/src/avs_vault_ticket.rs +++ b/restaking_core/src/avs_vault_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{RestakingCoreError, RestakingCoreResult}, @@ -62,6 +62,7 @@ impl AvsVaultTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("AvsVaultTicket is not active or in cooldown"); Err(RestakingCoreError::AvsVaultTicketInactive) } } diff --git a/restaking_core/src/operator_avs_ticket.rs b/restaking_core/src/operator_avs_ticket.rs index 1abae209..74c108ab 100644 --- a/restaking_core/src/operator_avs_ticket.rs +++ b/restaking_core/src/operator_avs_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{RestakingCoreError, RestakingCoreResult}, @@ -77,6 +77,7 @@ impl OperatorAvsTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("OperatorAvsTicket is not active or in cooldown"); Err(RestakingCoreError::OperatorAvsTicketNotActive) } } diff --git a/restaking_core/src/operator_vault_ticket.rs b/restaking_core/src/operator_vault_ticket.rs index c5d561f2..19e4f711 100644 --- a/restaking_core/src/operator_vault_ticket.rs +++ b/restaking_core/src/operator_vault_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{RestakingCoreError, RestakingCoreResult}, @@ -78,6 +78,7 @@ impl OperatorVaultTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("OperatorVaultTicket is not active or in cooldown"); Err(RestakingCoreError::OperatorVaultTicketInactive) } } diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 1d67627d..7135ec8f 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -113,7 +113,11 @@ impl OperatorDelegation { pub fn slash(&mut self, slash_amount: u64) -> VaultCoreResult<()> { let total_security_amount = self.total_security()?; if slash_amount > total_security_amount { - msg!("slashing too much"); + msg!( + "slash amount exceeds total security ({}>{})", + slash_amount, + total_security_amount + ); return Err(VaultCoreError::VaultSlashingUnderflow); } @@ -147,7 +151,7 @@ impl OperatorDelegation { // Ensure we've slashed the exact amount requested if remaining_slash > 0 { - msg!("slashing incomplete"); + msg!("slashing incomplete ({} remaining)", remaining_slash); return Err(VaultCoreError::VaultSlashingIncomplete); } diff --git a/vault_core/src/result.rs b/vault_core/src/result.rs index 3f93090d..82dcefcc 100644 --- a/vault_core/src/result.rs +++ b/vault_core/src/result.rs @@ -9,7 +9,7 @@ pub enum VaultCoreError { VaultFeeCalculationOverflow, VaultDataEmpty, VaultInvalidProgramOwner, - VaultInvalidData(String), + VaultInvalidData, VaultInvalidPda, VaultExpectedWritable, VaultSerializationFailed(String), @@ -116,6 +116,7 @@ pub enum VaultCoreError { VaultStakerWithdrawalTicketOverflow, VaultStakerWithdrawalTicketNotWithdrawable, VaultUndelegationUnderflow, + VaultDepositUnderflow, } impl From for ProgramError { diff --git a/vault_core/src/vault.rs b/vault_core/src/vault.rs index b8ede6a4..95278168 100644 --- a/vault_core/src/vault.rs +++ b/vault_core/src/vault.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -50,6 +50,9 @@ pub struct Vault { /// The total number of tokens deposited tokens_deposited: u64, + /// The amount of tokens that are reserved for withdrawal + withdrawable_reserve_amount: u64, + /// The deposit fee in basis points deposit_fee_bps: u16, @@ -97,6 +100,7 @@ impl Vault { vault_index, lrt_supply: 0, tokens_deposited: 0, + withdrawable_reserve_amount: 0, deposit_fee_bps, withdrawal_fee_bps, avs_count: 0, @@ -143,6 +147,36 @@ impl Vault { Ok(()) } + pub const fn withdrawable_reserve_amount(&self) -> u64 { + self.withdrawable_reserve_amount + } + + pub fn increment_withdrawable_reserve_amount(&mut self, amount: u64) -> VaultCoreResult<()> { + self.withdrawable_reserve_amount = self + .withdrawable_reserve_amount + .checked_add(amount) + .ok_or(VaultCoreError::VaultDepositOverflow)?; + Ok(()) + } + + pub fn decrement_withdrawable_reserve_amount(&mut self, amount: u64) -> VaultCoreResult<()> { + self.withdrawable_reserve_amount = self + .withdrawable_reserve_amount + .checked_sub(amount) + .ok_or(VaultCoreError::VaultDepositUnderflow)?; + Ok(()) + } + + pub const fn tokens_deposited(&self) -> u64 { + self.tokens_deposited + } + + pub fn max_delegation_amount(&self) -> VaultCoreResult { + self.tokens_deposited + .checked_sub(self.withdrawable_reserve_amount) + .ok_or(VaultCoreError::VaultDelegationUnderflow) + } + pub const fn lrt_mint(&self) -> Pubkey { self.lrt_mint } @@ -256,10 +290,6 @@ impl Vault { Ok(fee) } - pub const fn tokens_deposited(&self) -> u64 { - self.tokens_deposited - } - pub fn set_lrt_supply(&mut self, lrt_supply: u64) { self.lrt_supply = lrt_supply; } @@ -379,26 +409,33 @@ impl Vault { account: &AccountInfo, ) -> VaultCoreResult { if account.data_is_empty() { + msg!("Vault account data is empty"); return Err(VaultCoreError::VaultDataEmpty); } if account.owner != program_id { + msg!("Vault account owner is not the program id"); return Err(VaultCoreError::VaultInvalidProgramOwner); } - let state = Self::deserialize(&mut account.data.borrow_mut().as_ref()) - .map_err(|e| VaultCoreError::VaultInvalidData(e.to_string()))?; + let state = Self::deserialize(&mut account.data.borrow_mut().as_ref()).map_err(|e| { + msg!("Vault account deserialization failed: {}", e); + VaultCoreError::VaultInvalidData + })?; if !state.is_struct_valid() { - return Err(VaultCoreError::VaultInvalidData( - "Vault account header is invalid".to_string(), - )); + msg!("Vault account header is invalid"); + return Err(VaultCoreError::VaultInvalidData); } let mut seeds = Self::seeds(&state.base); seeds.push(vec![state.bump]); let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_ref()).collect(); - let expected_pubkey = Pubkey::create_program_address(&seeds_iter, program_id) - .map_err(|_| VaultCoreError::VaultInvalidPda)?; + let expected_pubkey = + Pubkey::create_program_address(&seeds_iter, program_id).map_err(|e| { + msg!("Vault account PDA creation failed: {}", e); + VaultCoreError::VaultInvalidPda + })?; if expected_pubkey != *account.key { + msg!("Vault account PDA is invalid"); return Err(VaultCoreError::VaultInvalidPda); } @@ -418,6 +455,7 @@ impl<'a, 'info> SanitizedVault<'a, 'info> { expect_writable: bool, ) -> VaultCoreResult> { if expect_writable && !account.is_writable { + msg!("Vault account is not writable"); return Err(VaultCoreError::VaultExpectedWritable); } let vault = Box::new(Vault::deserialize_checked(program_id, account)?); diff --git a/vault_core/src/vault_avs_slasher_operator_ticket.rs b/vault_core/src/vault_avs_slasher_operator_ticket.rs index 645acafd..91383d23 100644 --- a/vault_core/src/vault_avs_slasher_operator_ticket.rs +++ b/vault_core/src/vault_avs_slasher_operator_ticket.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -101,12 +101,16 @@ impl VaultAvsSlasherOperatorTicket { slash_amount: u64, max_slashable_per_epoch: u64, ) -> VaultCoreResult<()> { - if self + let new_slashed_amount = self .slashed .checked_add(slash_amount) - .ok_or(VaultCoreError::VaultAvsSlasherOperatorOverflow)? - > max_slashable_per_epoch - { + .ok_or(VaultCoreError::VaultAvsSlasherOperatorOverflow)?; + if new_slashed_amount > max_slashable_per_epoch { + msg!( + "Max slashable per epoch exceeded ({} > {})", + new_slashed_amount, + max_slashable_per_epoch + ); return Err(VaultCoreError::VaultAvsSlasherOperatorMaxSlashableExceeded); } Ok(()) diff --git a/vault_core/src/vault_avs_slasher_ticket.rs b/vault_core/src/vault_avs_slasher_ticket.rs index 003f5257..51789312 100644 --- a/vault_core/src/vault_avs_slasher_ticket.rs +++ b/vault_core/src/vault_avs_slasher_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -83,6 +83,7 @@ impl VaultAvsSlasherTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("VaultAvsSlasherTicket is not active or in cooldown"); Err(VaultCoreError::VaultAvsSlasherTicketInactive) } } diff --git a/vault_core/src/vault_avs_ticket.rs b/vault_core/src/vault_avs_ticket.rs index 119e2881..1dbc79b7 100644 --- a/vault_core/src/vault_avs_ticket.rs +++ b/vault_core/src/vault_avs_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -71,6 +71,7 @@ impl VaultAvsTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("VaultAvsTicket is not active or in cooldown"); Err(VaultCoreError::VaultAvsTicketInactive) } } diff --git a/vault_core/src/vault_delegation_list.rs b/vault_core/src/vault_delegation_list.rs index 89b1fcad..922b7351 100644 --- a/vault_core/src/vault_delegation_list.rs +++ b/vault_core/src/vault_delegation_list.rs @@ -16,6 +16,12 @@ pub enum UndelegateForWithdrawMethod { ProRata, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VaultDelegationUpdateSummary { + NotUpdated, + Updated { amount_reserved_for_withdraw: u64 }, +} + /// Represents the operators which have opted-in to this vault #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct VaultDelegationList { @@ -28,9 +34,6 @@ pub struct VaultDelegationList { /// the list of delegations delegations: Vec, - /// The reserve for withdrawable tokens - withdrawable_reserve_amount: u64, - /// The last slot the operator list was updated. /// Delegation information here is out of date if the last update epoch < current epoch last_slot_updated: u64, @@ -48,7 +51,6 @@ impl VaultDelegationList { account_type: AccountType::VaultDelegationList, vault, delegations: vec![], - withdrawable_reserve_amount: 0, last_slot_updated: 0, reserved: [0; 128], bump, @@ -67,18 +69,6 @@ impl VaultDelegationList { &self.delegations } - pub const fn withdrawable_reserve_amount(&self) -> u64 { - self.withdrawable_reserve_amount - } - - pub fn decrement_withdrawable_reserve_amount(&mut self, amount: u64) -> VaultCoreResult<()> { - self.withdrawable_reserve_amount = self - .withdrawable_reserve_amount - .checked_sub(amount) - .ok_or(VaultCoreError::VaultDelegationListAmountWithdrawableUnderflow)?; - Ok(()) - } - /// Returns the total security in the delegation list pub fn total_security(&self) -> VaultCoreResult { let mut total: u64 = 0; @@ -117,7 +107,7 @@ impl VaultDelegationList { #[inline(always)] pub fn check_update_needed(&self, slot: u64, epoch_length: u64) -> VaultCoreResult<()> { if self.is_update_needed(slot, epoch_length) { - msg!("update needed bro"); + msg!("Vault delegation list update required"); Err(VaultCoreError::VaultDelegationListUpdateRequired) } else { Ok(()) @@ -126,19 +116,24 @@ impl VaultDelegationList { /// Updates the delegation list for the current epoch if needed. #[inline(always)] - pub fn update(&mut self, slot: u64, epoch_length: u64) -> VaultCoreResult { + pub fn update( + &mut self, + slot: u64, + epoch_length: u64, + ) -> VaultCoreResult { let last_epoch_update = self.last_slot_updated.checked_div(epoch_length).unwrap(); let current_epoch = slot.checked_div(epoch_length).unwrap(); + let mut amount_reserved_for_withdraw: u64 = 0; + // time should only move forward, unwrap is safe let epoch_diff = current_epoch.checked_sub(last_epoch_update).unwrap(); match epoch_diff { - 0 => return Ok(false), + 0 => return Ok(VaultDelegationUpdateSummary::NotUpdated), 1 => { // enqueued -> cooling down, enqueued wiped for operator in self.delegations.iter_mut() { - self.withdrawable_reserve_amount = self - .withdrawable_reserve_amount + amount_reserved_for_withdraw = amount_reserved_for_withdraw .checked_add(operator.update()) .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; } @@ -150,8 +145,7 @@ impl VaultDelegationList { let amount_withdrawal_1 = operator.update(); let amount_withdrawal_2 = operator.update(); - self.withdrawable_reserve_amount = self - .withdrawable_reserve_amount + amount_reserved_for_withdraw = amount_reserved_for_withdraw .checked_add(amount_withdrawal_1) .and_then(|x| x.checked_add(amount_withdrawal_2)) .ok_or(VaultCoreError::VaultDelegationListUpdateOverflow)?; @@ -161,7 +155,9 @@ impl VaultDelegationList { self.last_slot_updated = slot; - Ok(true) + Ok(VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw, + }) } /// Delegates an amount of stake to an operator and ensures the amount delegated doesn't @@ -178,14 +174,13 @@ impl VaultDelegationList { &mut self, operator: Pubkey, amount: u64, - total_deposited: u64, + max_delegation_amount: u64, ) -> VaultCoreResult<()> { let delegated_security = self.total_security()?; // Ensure the amount delegated doesn't exceed the total deposited - let security_available_for_delegation = total_deposited + let security_available_for_delegation = max_delegation_amount .checked_sub(delegated_security) - .and_then(|x| x.checked_sub(self.withdrawable_reserve_amount)) .ok_or(VaultCoreError::VaultDelegationListInsufficientSecurity)?; if amount > security_available_for_delegation { @@ -470,7 +465,12 @@ mod tests { list.undelegate(operator, 30).unwrap(); // Simulate passing of one epoch - assert!(list.update(epoch_length, epoch_length).unwrap()); + assert_eq!( + list.update(epoch_length, epoch_length).unwrap(), + VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw: 0 + } + ); let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 70); @@ -478,49 +478,48 @@ mod tests { assert_eq!(delegation.enqueued_for_cooldown_amount(), 0); // Simulate passing of another epoch - assert!(list.update(epoch_length * 2, epoch_length).unwrap()); + assert_eq!( + list.update(epoch_length * 2, epoch_length).unwrap(), + VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw: 0 + } + ); let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 70); assert_eq!(delegation.cooling_down_amount(), 0); - assert_eq!(list.withdrawable_reserve_amount(), 0); } #[test] fn test_undelegate_for_withdraw_and_over_delegate() { let mut list = setup_vault_delegation_list(); let operator = Pubkey::new_unique(); - let total_deposited = 1000; - let initial_delegation = 500; - let undelegate_amount = 200; - let over_delegation_attempt = 600; - list.delegate(operator, initial_delegation, total_deposited) - .unwrap(); - list.undelegate_for_withdrawal(undelegate_amount, UndelegateForWithdrawMethod::ProRata) + list.delegate(operator, 500, 1000).unwrap(); + list.undelegate_for_withdrawal(200, UndelegateForWithdrawMethod::ProRata) .unwrap(); assert_eq!(list.delegations().len(), 1); let delegation = list.delegations().get(0).unwrap(); + assert_eq!(delegation.staked_amount(), 300); + assert_eq!(delegation.enqueued_for_withdraw_amount(), 200); + assert_eq!( - delegation.staked_amount(), - initial_delegation - undelegate_amount + list.update(100, 100).unwrap(), + VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw: 0 + } ); - assert_eq!(delegation.enqueued_for_withdraw_amount(), undelegate_amount); - assert_eq!(list.withdrawable_reserve_amount(), 0); - - assert!(list.update(100, 100).unwrap()); - assert!(list.update(200, 100).unwrap()); - assert_eq!(list.withdrawable_reserve_amount(), undelegate_amount); assert_eq!( - list.total_security().unwrap(), - initial_delegation - undelegate_amount + list.update(200, 100).unwrap(), + VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw: 200 + } ); + assert_eq!(list.total_security().unwrap(), 300); - // 1000 total deposits, 300 delegated, 200 enqueued for withdraw - // if try to delegate 600, should fail because some assets are set aside for withdraw assert_eq!( - list.delegate(operator, over_delegation_attempt, total_deposited), + list.delegate(operator, 701, 1000), Err(VaultCoreError::VaultDelegationListInsufficientSecurity) ); } @@ -897,9 +896,12 @@ mod tests { list.update(100, 100).unwrap(); list.undelegate_for_withdrawal(25_000, UndelegateForWithdrawMethod::ProRata) .unwrap(); - list.update(200, 100).unwrap(); - - assert_eq!(list.withdrawable_reserve_amount(), 25_000); + assert_eq!( + list.update(200, 100).unwrap(), + VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw: 25_000 + } + ); let delegation = list.delegations().get(0).unwrap(); assert_eq!(delegation.staked_amount(), 50000); diff --git a/vault_core/src/vault_operator_ticket.rs b/vault_core/src/vault_operator_ticket.rs index 92c3488c..f6abde14 100644 --- a/vault_core/src/vault_operator_ticket.rs +++ b/vault_core/src/vault_operator_ticket.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use jito_jsm_core::slot_toggled_field::SlotToggle; -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; use crate::{ result::{VaultCoreError, VaultCoreResult}, @@ -69,6 +69,7 @@ impl VaultOperatorTicket { if self.state.is_active_or_cooldown(slot, epoch_length) { Ok(()) } else { + msg!("VaultOperatorTicket is not active or in cooldown"); Err(VaultCoreError::VaultOperatorTicketInactive) } } diff --git a/vault_program/src/add_delegation.rs b/vault_program/src/add_delegation.rs index 2a0ac899..673ed16a 100644 --- a/vault_program/src/add_delegation.rs +++ b/vault_program/src/add_delegation.rs @@ -48,7 +48,7 @@ pub fn process_add_delegation( vault_delegation_list.vault_delegation_list_mut().delegate( *operator.key, amount, - vault.vault().tokens_deposited(), + vault.vault().max_delegation_amount()?, )?; vault_delegation_list.save_with_realloc(&Rent::get()?, payer.account())?; diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index aa264727..6b263504 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -22,6 +22,7 @@ use solana_program::{ sysvar::Sysvar, }; use spl_token::instruction::{burn, close_account, transfer}; +use std::cmp::min; /// Burns the withdrawal ticket, transferring the assets to the staker and closing the withdrawal ticket. /// @@ -34,7 +35,7 @@ pub fn process_burn_withdrawal_ticket( let SanitizedAccounts { config, mut vault, - mut vault_delegation_list, + vault_delegation_list, mut vault_token_account, mut lrt_mint, staker, @@ -57,11 +58,9 @@ pub fn process_burn_withdrawal_ticket( "LRT mint mismatch", )?; - msg!("1"); vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .check_withdrawable(slot, epoch_length)?; - msg!("2"); // find the current redemption amount and the original redemption amount in the withdraw ticket let redemption_amount = vault.vault().calculate_assets_returned_amount( @@ -69,12 +68,10 @@ pub fn process_burn_withdrawal_ticket( .vault_staker_withdrawal_ticket() .lrt_amount(), )?; - msg!("3"); let original_redemption_amount = vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .withdraw_allocation_amount(); - msg!("4"); let actual_withdraw_amount = if redemption_amount > original_redemption_amount { // The program can guarantee the original redemption amount, but if the redemption amount @@ -88,9 +85,7 @@ pub fn process_burn_withdrawal_ticket( let delegated_security_in_vault = vault_delegation_list .vault_delegation_list() .total_security()?; - let assets_reserved_for_withdrawal_tickets = vault_delegation_list - .vault_delegation_list() - .withdrawable_reserve_amount(); + let assets_reserved_for_withdrawal_tickets = vault.vault().withdrawable_reserve_amount(); let available_unstaked_assets = tokens_deposited_in_vault .checked_sub(delegated_security_in_vault) @@ -109,19 +104,16 @@ pub fn process_burn_withdrawal_ticket( } else { redemption_amount }; - msg!("5"); let lrt_to_burn = vault .vault() .calculate_lrt_mint_amount(actual_withdraw_amount)?; - msg!("6"); let lrt_amount_to_burn = std::cmp::min( lrt_to_burn, vault_staker_withdrawal_ticket .vault_staker_withdrawal_ticket() .lrt_amount(), ); - msg!("7"); _burn_lrt( program_id, @@ -132,11 +124,9 @@ pub fn process_burn_withdrawal_ticket( &lrt_mint, lrt_amount_to_burn, )?; - msg!("8"); lrt_mint.reload()?; vault_staker_withdrawal_ticket_token_account.reload()?; - msg!("9"); _transfer_vault_tokens_to_staker( program_id, &vault, @@ -150,9 +140,7 @@ pub fn process_burn_withdrawal_ticket( msg!( "decrementing reserve amount: {:?}, amount available: {:?}", original_redemption_amount, - vault_delegation_list - .vault_delegation_list() - .withdrawable_reserve_amount() + vault.vault().withdrawable_reserve_amount() ); // Decrement the amount reserved for withdraw tickets because it's been claimed now. @@ -161,11 +149,10 @@ pub fn process_burn_withdrawal_ticket( // where withdrawal funds are cooling down, the slashing event will be applied to the withdraw reserve on the operator // which propagates to this. if there's a slashing after the withdrawed funds are fully cooled down and ready, it won't // show up in this. how do we reconcile this? - vault_delegation_list - .vault_delegation_list_mut() + vault + .vault_mut() .decrement_withdrawable_reserve_amount(original_redemption_amount)?; - msg!("set tokens deposited"); // refresh after burn vault .vault_mut() @@ -353,38 +340,30 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(accounts_iter)?, false)?; - msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(accounts_iter)?, true)?; - msg!("b"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(accounts_iter)?, true, vault.account().key, )?; - msg!("c"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; - msg!("d"); let lrt_mint = SanitizedTokenMint::sanitize(next_account_info(accounts_iter)?, true)?; - msg!("e"); let staker = SanitizedSignerAccount::sanitize(next_account_info(accounts_iter)?, true)?; - msg!("f"); let staker_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().supported_mint(), staker.account().key, )?; - msg!("g"); let staker_lrt_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), staker.account().key, )?; - msg!("h"); let vault_staker_withdrawal_ticket = SanitizedVaultStakerWithdrawalTicket::sanitize( program_id, next_account_info(accounts_iter)?, @@ -392,14 +371,12 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { staker.account().key, true, )?; - msg!("i"); let vault_staker_withdrawal_ticket_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(accounts_iter)?, &vault.vault().lrt_mint(), vault_staker_withdrawal_ticket.account().key, )?; - msg!("j"); let _token_program = SanitizedTokenProgram::sanitize(next_account_info(accounts_iter)?)?; let _system_program = SanitizedSystemProgram::sanitize(next_account_info(accounts_iter)?)?; diff --git a/vault_program/src/slash.rs b/vault_program/src/slash.rs index 4e122273..0505f2a1 100644 --- a/vault_program/src/slash.rs +++ b/vault_program/src/slash.rs @@ -22,7 +22,6 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, - msg, program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, @@ -55,13 +54,11 @@ pub fn process_slash( slasher_token_account, } = SanitizedAccounts::sanitize(program_id, accounts, slot)?; - msg!("checking update needed"); vault_delegation_list .vault_delegation_list_mut() .check_update_needed(slot, config.config().epoch_length())?; // The vault shall be opted-in to the AVS and the AVS shall be opted-in to the vault - msg!("vault <> avs check"); vault_avs_ticket .vault_avs_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; @@ -70,7 +67,6 @@ pub fn process_slash( .check_active_or_cooldown(slot, config.config().epoch_length())?; // The operator shall be opted-in to vault and the vault shall be staked to the operator - msg!("operator <> vault check"); operator_vault_ticket .operator_vault_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; @@ -79,7 +75,6 @@ pub fn process_slash( .check_active_or_cooldown(slot, config.config().epoch_length())?; // The operator shall be opted-in to the AVS and the AVS shall be opted-in to the operator - msg!("avs <> operator check"); avs_operator_ticket .avs_operator_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; @@ -87,7 +82,6 @@ pub fn process_slash( .operator_avs_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; // The slasher shall be active for the AVS and the vault - msg!("avs <> vault check"); avs_vault_slasher_ticket .avs_vault_slasher_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; @@ -95,7 +89,6 @@ pub fn process_slash( .vault_avs_slasher_ticket() .check_active_or_cooldown(slot, config.config().epoch_length())?; - msg!("max exceeded check"); let max_slashable_per_epoch = vault_avs_slasher_ticket .vault_avs_slasher_ticket() .max_slashable_per_epoch(); @@ -103,14 +96,12 @@ pub fn process_slash( .vault_avs_slasher_operator_ticket() .check_max_slashable_not_exceeded(slash_amount, max_slashable_per_epoch)?; - msg!("slashing"); vault_delegation_list .vault_delegation_list_mut() .slash(operator.account().key, slash_amount)?; vault_avs_slasher_operator_ticket .vault_avs_slasher_operator_ticket_mut() .increment_slashed_amount(slash_amount)?; - msg!("sending slashed funds"); _transfer_slashed_funds( &vault, &vault_token_account, @@ -193,25 +184,20 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { let config = SanitizedConfig::sanitize(program_id, next_account_info(&mut accounts_iter)?, false)?; - msg!("a"); let vault = SanitizedVault::sanitize(program_id, next_account_info(&mut accounts_iter)?, true)?; - msg!("b"); let avs = SanitizedAvs::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; - msg!("c"); let operator = SanitizedOperator::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, false, )?; - msg!("d"); let slasher = SanitizedSignerAccount::sanitize(next_account_info(&mut accounts_iter)?, false)?; - msg!("e"); let avs_operator_ticket = SanitizedAvsOperatorTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -219,7 +205,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, operator.account().key, )?; - msg!("f"); let operator_avs_ticket = SanitizedOperatorAvsTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -227,7 +212,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, avs.account().key, )?; - msg!("g"); let avs_vault_ticket = SanitizedAvsVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -235,7 +219,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, vault.account().key, )?; - msg!("h"); let operator_vault_ticket = SanitizedOperatorVaultTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -243,7 +226,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { operator.account().key, vault.account().key, )?; - msg!("i"); let vault_avs_ticket = SanitizedVaultAvsTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -251,7 +233,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, avs.account().key, )?; - msg!("j"); let vault_operator_ticket = SanitizedVaultOperatorTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -259,7 +240,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, operator.account().key, )?; - msg!("k"); let avs_vault_slasher_ticket = SanitizedAvsVaultSlasherTicket::sanitize( &config.config().restaking_program(), next_account_info(&mut accounts_iter)?, @@ -268,7 +248,6 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { vault.account().key, slasher.account().key, )?; - msg!("l"); let vault_avs_slasher_ticket = SanitizedVaultAvsSlasherTicket::sanitize( program_id, next_account_info(&mut accounts_iter)?, @@ -277,14 +256,12 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { avs.account().key, slasher.account().key, )?; - msg!("m"); let vault_delegation_list = SanitizedVaultDelegationList::sanitize( program_id, next_account_info(&mut accounts_iter)?, true, vault.account().key, )?; - msg!("n"); let epoch = slot.checked_div(config.config().epoch_length()).unwrap(); let vault_avs_slasher_operator_ticket = SanitizedVaultAvsSlasherOperatorTicket::sanitize( program_id, @@ -297,13 +274,11 @@ impl<'a, 'info> SanitizedAccounts<'a, 'info> { epoch, )?; - msg!("o"); let vault_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), vault.account().key, )?; - msg!("p"); let slasher_token_account = SanitizedAssociatedTokenAccount::sanitize( next_account_info(&mut accounts_iter)?, &vault.vault().supported_mint(), diff --git a/vault_program/src/update_vault.rs b/vault_program/src/update_vault.rs index d63ec663..9daef459 100644 --- a/vault_program/src/update_vault.rs +++ b/vault_program/src/update_vault.rs @@ -1,7 +1,8 @@ use jito_restaking_sanitization::associated_token_account::SanitizedAssociatedTokenAccount; use jito_vault_core::{ - config::SanitizedConfig, vault::SanitizedVault, - vault_delegation_list::SanitizedVaultDelegationList, + config::SanitizedConfig, + vault::SanitizedVault, + vault_delegation_list::{SanitizedVaultDelegationList, VaultDelegationUpdateSummary}, }; use solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -22,9 +23,16 @@ pub fn process_update_vault(program_id: &Pubkey, accounts: &[AccountInfo]) -> Pr let slot = Clock::get()?.slot; - vault_delegation_list + if let VaultDelegationUpdateSummary::Updated { + amount_reserved_for_withdraw, + } = vault_delegation_list .vault_delegation_list_mut() - .update(slot, config.config().epoch_length())?; + .update(slot, config.config().epoch_length())? + { + vault + .vault_mut() + .increment_withdrawable_reserve_amount(amount_reserved_for_withdraw)?; + } vault .vault_mut() .set_tokens_deposited(vault_token_account.token_account().amount); From 3649b813b8dd59872f6fad60e2eace529ac5cd28 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Thu, 8 Aug 2024 23:11:03 -0500 Subject: [PATCH 20/22] werd --- vault_program/src/burn_withdrawal_ticket.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index 6b263504..0f8d8d87 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -22,7 +22,6 @@ use solana_program::{ sysvar::Sysvar, }; use spl_token::instruction::{burn, close_account, transfer}; -use std::cmp::min; /// Burns the withdrawal ticket, transferring the assets to the staker and closing the withdrawal ticket. /// From 6d8626b79500fb900dd706776c7974608d33b0a6 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Thu, 8 Aug 2024 23:31:34 -0500 Subject: [PATCH 21/22] werd --- .../tests/vault/burn_withdrawal_ticket.rs | 126 ++++++++++-------- vault_core/src/operator_delegation.rs | 2 +- vault_program/src/burn_withdrawal_ticket.rs | 11 +- 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/integration_tests/tests/vault/burn_withdrawal_ticket.rs b/integration_tests/tests/vault/burn_withdrawal_ticket.rs index 7b4df43a..ba6a94f2 100644 --- a/integration_tests/tests/vault/burn_withdrawal_ticket.rs +++ b/integration_tests/tests/vault/burn_withdrawal_ticket.rs @@ -18,6 +18,7 @@ mod tests { operator_root: OperatorRoot, depositor: Keypair, withdrawal_ticket_base: Pubkey, + slasher: Keypair, } async fn setup_withdrawal_ticket( @@ -30,6 +31,7 @@ mod tests { deposit_amount: u64, delegate_amount: u64, withdrawal_amount: u64, + max_slash_amount: u64, ) -> PreparedWithdrawalTicket { // Setup vault with initial deposit let (_vault_config_admin, vault_root) = vault_program_client @@ -136,6 +138,50 @@ mod tests { .await .unwrap(); + // create slasher w/ token account + let slasher = Keypair::new(); + fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); + fixture + .create_ata(&vault.supported_mint(), &slasher.pubkey()) + .await + .unwrap(); + + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + // do all the opt-in stuff for the slasher + restaking_program_client + .avs_vault_slasher_opt_in( + &avs_root, + &vault_root.vault_pubkey, + &slasher.pubkey(), + max_slash_amount, + ) + .await + .unwrap(); + + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + + vault_program_client + .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) + .await + .unwrap(); + + fixture + .warp_slot_incremental(2 * config.epoch_length()) + .await + .unwrap(); + + vault_program_client + .do_update_vault(&vault_root.vault_pubkey) + .await + .unwrap(); + let VaultStakerWithdrawalTicketRoot { base } = vault_program_client .do_enqueue_withdraw(&vault_root, &depositor, withdrawal_amount) .await @@ -147,6 +193,7 @@ mod tests { operator_root, depositor, withdrawal_ticket_base: base, + slasher, } } @@ -163,6 +210,7 @@ mod tests { operator_root: _, depositor, withdrawal_ticket_base, + slasher: _, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -173,6 +221,7 @@ mod tests { 100, 100, 100, + 100, ) .await; @@ -196,6 +245,7 @@ mod tests { operator_root: _, depositor, withdrawal_ticket_base, + slasher: _, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -206,6 +256,7 @@ mod tests { 100, 100, 100, + 100, ) .await; @@ -243,6 +294,7 @@ mod tests { operator_root: _, depositor, withdrawal_ticket_base, + slasher: _, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -253,6 +305,7 @@ mod tests { 1000, 1000, 1000, + 100, ) .await; @@ -306,6 +359,7 @@ mod tests { operator_root: _, depositor, withdrawal_ticket_base, + slasher: _, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -316,6 +370,7 @@ mod tests { 1000, 1000, 1000, + 100, ) .await; @@ -376,6 +431,7 @@ mod tests { operator_root, depositor, withdrawal_ticket_base, + slasher: _, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -386,6 +442,7 @@ mod tests { 1000, 1000, 1000, + 100, ) .await; @@ -458,8 +515,6 @@ mod tests { assert_eq!(vault_token_account.amount, 100); } - /// This test is complicated... TODO (LB): need to figure out this logic guh - /// /// The user withdrew at some ratio of the vault, but a slashing took place while the withdrawal ticket /// was maturing. The user gets back less than they originally anticipated and the amount of withdrawal /// set aside is reduced to 0. @@ -478,6 +533,7 @@ mod tests { // operator_root, // depositor, // withdrawal_ticket_base, + // slasher, // } = setup_withdrawal_ticket( // &mut fixture, // &mut vault_program_client, @@ -488,6 +544,7 @@ mod tests { // 1000, // 1000, // 1000, + // 100, // ) // .await; // @@ -501,35 +558,6 @@ mod tests { // .await // .unwrap(); // - // // create slasher w/ token account - // let slasher = Keypair::new(); - // fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); - // fixture - // .create_ata(&vault.supported_mint(), &slasher.pubkey()) - // .await - // .unwrap(); - // - // // do all the opt-in stuff - // restaking_program_client - // .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) - // .await - // .unwrap(); - // - // fixture - // .warp_slot_incremental(2 * config.epoch_length()) - // .await - // .unwrap(); - // - // vault_program_client - // .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) - // .await - // .unwrap(); - // - // fixture - // .warp_slot_incremental(2 * config.epoch_length()) - // .await - // .unwrap(); - // // vault_program_client // .setup_vault_avs_slasher_operator_ticket( // &vault_root, @@ -539,6 +567,12 @@ mod tests { // ) // .await // .unwrap(); + // + // vault_program_client + // .do_update_vault(&vault_root.vault_pubkey) + // .await + // .unwrap(); + // // vault_program_client // .do_slash( // &vault_root, @@ -585,6 +619,7 @@ mod tests { operator_root, depositor, withdrawal_ticket_base, + slasher, } = setup_withdrawal_ticket( &mut fixture, &mut vault_program_client, @@ -595,6 +630,7 @@ mod tests { 1000, 1000, 900, + 100, ) .await; @@ -603,33 +639,6 @@ mod tests { .await .unwrap(); - // create slasher w/ token account - let slasher = Keypair::new(); - fixture.transfer(&slasher.pubkey(), 100.0).await.unwrap(); - fixture - .create_ata(&vault.supported_mint(), &slasher.pubkey()) - .await - .unwrap(); - - // do all the opt-in stuff - restaking_program_client - .avs_vault_slasher_opt_in(&avs_root, &vault_root.vault_pubkey, &slasher.pubkey(), 100) - .await - .unwrap(); - let restaking_config = restaking_program_client - .get_config(&RestakingConfig::find_program_address(&jito_restaking_program::id()).0) - .await - .unwrap(); - fixture - .warp_slot_incremental(2 * restaking_config.epoch_length()) - .await - .unwrap(); - - vault_program_client - .vault_avs_vault_slasher_opt_in(&vault_root, &avs_root.avs_pubkey, &slasher.pubkey()) - .await - .unwrap(); - let config = vault_program_client .get_config(&Config::find_program_address(&jito_vault_program::id()).0) .await @@ -638,6 +647,7 @@ mod tests { .warp_slot_incremental(2 * config.epoch_length()) .await .unwrap(); + vault_program_client .do_update_vault(&vault_root.vault_pubkey) .await diff --git a/vault_core/src/operator_delegation.rs b/vault_core/src/operator_delegation.rs index 7135ec8f..21e9aacf 100644 --- a/vault_core/src/operator_delegation.rs +++ b/vault_core/src/operator_delegation.rs @@ -114,7 +114,7 @@ impl OperatorDelegation { let total_security_amount = self.total_security()?; if slash_amount > total_security_amount { msg!( - "slash amount exceeds total security ({}>{})", + "slash amount exceeds total security (slash_amount: {}, total_security: {})", slash_amount, total_security_amount ); diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index 0f8d8d87..890e1dcf 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -142,12 +142,11 @@ pub fn process_burn_withdrawal_ticket( vault.vault().withdrawable_reserve_amount() ); - // Decrement the amount reserved for withdraw tickets because it's been claimed now. - // This part is tricky in the event the ratio - // TODO (LB): this is tricky... if there's a withdraw ticket and there's a slashing event in the epoch - // where withdrawal funds are cooling down, the slashing event will be applied to the withdraw reserve on the operator - // which propagates to this. if there's a slashing after the withdrawed funds are fully cooled down and ready, it won't - // show up in this. how do we reconcile this? + // TODO (LB): this logic needs to be fixed and is broken + // If a withdraw ticket is created and there is a slashing event before the withdraw ticket + // has fully matured, the program can end up in a situation where the original_redemption_amount + // is greater than the total withdrawable_reserve_amount. This is a bug and needs to be fixed. + // see test_burn_withdrawal_ticket_with_slashing_before_update vault .vault_mut() .decrement_withdrawable_reserve_amount(original_redemption_amount)?; From 3e0d7b7076ac724548c884cd87fc2e9c463a03f9 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Thu, 8 Aug 2024 23:35:02 -0500 Subject: [PATCH 22/22] werd --- vault_program/src/burn_withdrawal_ticket.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault_program/src/burn_withdrawal_ticket.rs b/vault_program/src/burn_withdrawal_ticket.rs index 890e1dcf..00084c85 100644 --- a/vault_program/src/burn_withdrawal_ticket.rs +++ b/vault_program/src/burn_withdrawal_ticket.rs @@ -142,7 +142,7 @@ pub fn process_burn_withdrawal_ticket( vault.vault().withdrawable_reserve_amount() ); - // TODO (LB): this logic needs to be fixed and is broken + // TODO (LB): https://github.com/jito-foundation/restaking/issues/24 // If a withdraw ticket is created and there is a slashing event before the withdraw ticket // has fully matured, the program can end up in a situation where the original_redemption_amount // is greater than the total withdrawable_reserve_amount. This is a bug and needs to be fixed.