From 08e60b580d3a317d0b6a914c6e300f65761f0922 Mon Sep 17 00:00:00 2001 From: Lucas B Date: Fri, 2 Aug 2024 13:56:25 -0500 Subject: [PATCH] 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,