diff --git a/dlp-api/src/instruction_builder/commit_finalize.rs b/dlp-api/src/instruction_builder/commit_finalize.rs index 0da78047..03b1dbc1 100644 --- a/dlp-api/src/instruction_builder/commit_finalize.rs +++ b/dlp-api/src/instruction_builder/commit_finalize.rs @@ -3,6 +3,10 @@ use dlp::{ delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, discriminator::DlpDiscriminator, + pda::{ + fees_vault_pda, undelegate_buffer_pda_from_delegated_account, + undelegation_request_pda_from_delegated_account, + }, pod_view::PodView, total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, @@ -13,7 +17,7 @@ use solana_program::{ }; use solana_sdk_ids::system_program; -use crate::compat::Modernize; +use crate::compat::{Compatize, Modernize}; pub struct CommitPDAs { pub delegation_record: Pubkey, @@ -26,9 +30,12 @@ pub struct CommitPDAs { pub fn commit_finalize( validator: Pubkey, delegated_account: Pubkey, + owner_program: Pubkey, + rent_reimbursement: Pubkey, args: &mut CommitFinalizeArgs, state_or_diff: &[u8], ) -> (Instruction, CommitPDAs) { + let delegated_account_compat = delegated_account.compatize(); let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), &dlp::id().modernize(), @@ -43,6 +50,15 @@ pub fn commit_finalize( validator_fees_vault_seeds_from_validator!(validator), &dlp::id().modernize(), ); + let undelegate_buffer_pda = + undelegate_buffer_pda_from_delegated_account(&delegated_account_compat) + .modernize(); + let undelegation_request_pda = + undelegation_request_pda_from_delegated_account( + &delegated_account_compat, + ) + .modernize(); + let fees_vault_pda = fees_vault_pda().modernize(); // save the bumps in the args args.bumps = CommitBumps { @@ -61,6 +77,11 @@ pub fn commit_finalize( AccountMeta::new(delegation_metadata.0, false), AccountMeta::new(validator_fees_vault.0, false), AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(owner_program, false), + AccountMeta::new(undelegate_buffer_pda, false), + AccountMeta::new(rent_reimbursement, false), + AccountMeta::new(fees_vault_pda, false), + AccountMeta::new(undelegation_request_pda, false), ], data: [ DlpDiscriminator::CommitFinalize.to_vec(), @@ -91,5 +112,10 @@ pub fn commit_finalize_size_budget(delegated_account: AccountSizeClass) -> u32 { AccountSizeClass::Tiny, // delegation_metadata_pda AccountSizeClass::Tiny, // validator_fees_vault_pda AccountSizeClass::Tiny, // system_program + AccountSizeClass::Tiny, // owner_program + delegated_account, // undelegate_buffer_pda + AccountSizeClass::Tiny, // rent_reimbursement + AccountSizeClass::Tiny, // fees_vault_pda + AccountSizeClass::Tiny, // undelegation_request_pda ]) } diff --git a/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs index 8d9b0ab3..48b1437a 100644 --- a/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs @@ -3,6 +3,10 @@ use dlp::{ delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, discriminator::DlpDiscriminator, + pda::{ + fees_vault_pda, undelegate_buffer_pda_from_delegated_account, + undelegation_request_pda_from_delegated_account, + }, pod_view::PodView, total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, @@ -13,7 +17,7 @@ use solana_program::{ }; use solana_sdk_ids::system_program; -use crate::compat::Modernize; +use crate::compat::{Compatize, Modernize}; /// Builds a commit state from buffer instruction. /// See [dlp::processor::process_commit_diff_from_buffer] for docs. @@ -21,8 +25,11 @@ pub fn commit_finalize_from_buffer( validator: Pubkey, delegated_account: Pubkey, data_buffer: Pubkey, + owner_program: Pubkey, + rent_reimbursement: Pubkey, commit_args: &mut CommitFinalizeArgs, ) -> (Instruction, super::CommitPDAs) { + let delegated_account_compat = delegated_account.compatize(); let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), &dlp::id().modernize(), @@ -37,6 +44,15 @@ pub fn commit_finalize_from_buffer( delegation_metadata_seeds_from_delegated_account!(delegated_account), &dlp::id().modernize(), ); + let undelegate_buffer_pda = + undelegate_buffer_pda_from_delegated_account(&delegated_account_compat) + .modernize(); + let undelegation_request_pda = + undelegation_request_pda_from_delegated_account( + &delegated_account_compat, + ) + .modernize(); + let fees_vault_pda = fees_vault_pda().modernize(); // save the bumps in the args commit_args.bumps = CommitBumps { @@ -56,6 +72,11 @@ pub fn commit_finalize_from_buffer( AccountMeta::new_readonly(data_buffer, false), AccountMeta::new(validator_fees_vault.0, false), AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(owner_program, false), + AccountMeta::new(undelegate_buffer_pda, false), + AccountMeta::new(rent_reimbursement, false), + AccountMeta::new(fees_vault_pda, false), + AccountMeta::new(undelegation_request_pda, false), ], data: [ DlpDiscriminator::CommitFinalizeFromBuffer.to_vec(), @@ -87,7 +108,11 @@ pub fn commit_finalize_from_buffer_size_budget( AccountSizeClass::Tiny, // delegation_metadata_pda delegated_account, // data_buffer AccountSizeClass::Tiny, // validator_fees_vault_pda - AccountSizeClass::Tiny, // program_config_pda AccountSizeClass::Tiny, // system_program + AccountSizeClass::Tiny, // owner_program + delegated_account, // undelegate_buffer_pda + AccountSizeClass::Tiny, // rent_reimbursement + AccountSizeClass::Tiny, // fees_vault_pda + AccountSizeClass::Tiny, // undelegation_request_pda ]) } diff --git a/dlp-api/src/instruction_builder/finalize.rs b/dlp-api/src/instruction_builder/finalize.rs index 52a189c1..be06235d 100644 --- a/dlp-api/src/instruction_builder/finalize.rs +++ b/dlp-api/src/instruction_builder/finalize.rs @@ -4,7 +4,9 @@ use dlp::{ commit_record_pda_from_delegated_account, commit_state_pda_from_delegated_account, delegation_metadata_pda_from_delegated_account, - delegation_record_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, fees_vault_pda, + undelegate_buffer_pda_from_delegated_account, + undelegation_request_pda_from_delegated_account, validator_fees_vault_pda_from_validator, }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, @@ -19,7 +21,12 @@ use crate::compat::{Compatize, Modernize}; /// Builds a finalize state instruction. /// See [dlp::processor::process_finalize] for docs. -pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { +pub fn finalize( + validator: Pubkey, + delegated_account: Pubkey, + owner_program: Pubkey, + rent_reimbursement: Pubkey, +) -> Instruction { let validator_compat = validator.compatize(); let delegated_account_compat = delegated_account.compatize(); let commit_state_pda = @@ -36,6 +43,15 @@ pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { &delegated_account_compat, ) .modernize(); + let fees_vault_pda = fees_vault_pda().modernize(); + let undelegate_buffer_pda = + undelegate_buffer_pda_from_delegated_account(&delegated_account_compat) + .modernize(); + let undelegation_request_pda = + undelegation_request_pda_from_delegated_account( + &delegated_account_compat, + ) + .modernize(); let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator_compat).modernize(); Instruction { @@ -49,6 +65,11 @@ pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { AccountMeta::new(delegation_metadata_pda, false), AccountMeta::new(validator_fees_vault_pda, false), AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(owner_program, false), + AccountMeta::new(undelegate_buffer_pda, false), + AccountMeta::new(rent_reimbursement, false), + AccountMeta::new(fees_vault_pda, false), + AccountMeta::new(undelegation_request_pda, false), ], data: DlpDiscriminator::Finalize.to_vec(), } @@ -70,5 +91,10 @@ pub fn finalize_size_budget(delegated_account: AccountSizeClass) -> u32 { AccountSizeClass::Tiny, // delegation_metadata_pda AccountSizeClass::Tiny, // validator_fees_vault_pda AccountSizeClass::Tiny, // system_program + AccountSizeClass::Tiny, // owner_program + delegated_account, // undelegate_buffer_pda + AccountSizeClass::Tiny, // rent_reimbursement + AccountSizeClass::Tiny, // fees_vault_pda + AccountSizeClass::Tiny, // undelegation_request_pda ]) } diff --git a/dlp-api/src/state/undelegation_request.rs b/dlp-api/src/state/undelegation_request.rs index dbc8411f..844486a2 100644 --- a/dlp-api/src/state/undelegation_request.rs +++ b/dlp-api/src/state/undelegation_request.rs @@ -19,6 +19,14 @@ pub struct UndelegationRequest { pub owner_program: Pubkey, /// The account that paid rent for this request PDA. + /// + /// Owner-program requests intentionally use the same account as + /// `DelegationMetadata::rent_payer`. That matches the common case where + /// the delegation payer also requests undelegation, and it lets validators + /// process auto-undelegation by fetching one account (delegation metadata only) + /// instead of two accounts and it also keeps the tx-size smaller. The request + /// PDA can be derived and passed without fetching it just to discover + /// a second close reimbursement account. pub rent_payer: Pubkey, /// The slot at which the request was created. diff --git a/src/processor/fast/commit_finalize.rs b/src/processor/fast/commit_finalize.rs index e0718e39..e614e24a 100644 --- a/src/processor/fast/commit_finalize.rs +++ b/src/processor/fast/commit_finalize.rs @@ -5,11 +5,13 @@ use crate::{ args::CommitFinalizeArgsWithBuffer, processor::fast::{ internal::{ + parse_auto_undelegation_accounts, + process_auto_undelegation_if_requested, process_commit_finalize_internal, CommitFinalizeInternalArgs, }, NewState, }, - require_n_accounts, DiffSet, + require_n_accounts_with_optionals, DiffSet, }; /// Commit a new state, or a diff, directly to the delegated account. Unlike, CommitState and @@ -32,14 +34,18 @@ pub fn process_commit_finalize( accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let [ - validator, // force multi-line - delegated_account, - delegation_record_account, - delegation_metadata_account, - validator_fees_vault, - _system_program, - ] = require_n_accounts!(accounts, 6); + let ( + [ + validator, // force multi-line + delegated_account, + delegation_record_account, + delegation_metadata_account, + validator_fees_vault, + system_program, + ], + optional_accounts, + ) = require_n_accounts_with_optionals!(accounts, 6); + let auto_accounts = parse_auto_undelegation_accounts(optional_accounts)?; let args = CommitFinalizeArgsWithBuffer::from_bytes(data)?; @@ -64,5 +70,15 @@ pub fn process_commit_finalize( validator_fees_vault, }; - process_commit_finalize_internal(commit_args) + let requester = process_commit_finalize_internal(commit_args)?; + process_auto_undelegation_if_requested( + requester, + validator, + delegated_account, + delegation_record_account, + delegation_metadata_account, + validator_fees_vault, + system_program, + auto_accounts, + ) } diff --git a/src/processor/fast/commit_finalize_from_buffer.rs b/src/processor/fast/commit_finalize_from_buffer.rs index 19826c06..c23be912 100644 --- a/src/processor/fast/commit_finalize_from_buffer.rs +++ b/src/processor/fast/commit_finalize_from_buffer.rs @@ -6,11 +6,13 @@ use crate::{ pod_view::PodView, processor::fast::{ internal::{ + parse_auto_undelegation_accounts, + process_auto_undelegation_if_requested, process_commit_finalize_internal, CommitFinalizeInternalArgs, }, NewState, }, - require_n_accounts, DiffSet, + require_n_accounts_with_optionals, DiffSet, }; /// Commit a new state of a delegated PDA @@ -48,15 +50,19 @@ pub fn process_commit_finalize_from_buffer( accounts: &[AccountView], data: &[u8], ) -> ProgramResult { - let [ - validator, // force multi-line - delegated_account, - delegation_record_account, - delegation_metadata_account, - data_account, // full bytes or diff - validator_fees_vault, - _system_program, - ] = require_n_accounts!(accounts, 7); + let ( + [ + validator, // force multi-line + delegated_account, + delegation_record_account, + delegation_metadata_account, + data_account, // full bytes or diff + validator_fees_vault, + system_program, + ], + optional_accounts, + ) = require_n_accounts_with_optionals!(accounts, 7); + let auto_accounts = parse_auto_undelegation_accounts(optional_accounts)?; let args = CommitFinalizeArgs::try_view_from(data)?; @@ -82,5 +88,15 @@ pub fn process_commit_finalize_from_buffer( validator_fees_vault, }; - process_commit_finalize_internal(commit_args) + let requester = process_commit_finalize_internal(commit_args)?; + process_auto_undelegation_if_requested( + requester, + validator, + delegated_account, + delegation_record_account, + delegation_metadata_account, + validator_fees_vault, + system_program, + auto_accounts, + ) } diff --git a/src/processor/fast/commit_state.rs b/src/processor/fast/commit_state.rs index 70fbcee3..47159548 100644 --- a/src/processor/fast/commit_state.rs +++ b/src/processor/fast/commit_state.rs @@ -167,18 +167,20 @@ pub(crate) fn process_commit_state_internal( return Err(DlpError::NonceOutOfOrder.into()); } - // Once undelegation has been requested, any subsequent commit should fail. - if delegation_metadata.undelegation_requester - == UndelegationRequester::Validator - { - log!("delegation metadata already has an undelegation requester: "); - args.delegation_metadata_account.address().log(); - return Err(DlpError::AlreadyUndelegated.into()); + match delegation_metadata.undelegation_requester { + UndelegationRequester::None => { + delegation_metadata.undelegation_requester = + UndelegationRequester::from_allow_undelegation( + args.allow_undelegation, + ); + } + UndelegationRequester::OwnerProgram => {} + UndelegationRequester::Validator => { + log!("delegation metadata already has an undelegation requester: "); + args.delegation_metadata_account.address().log(); + return Err(DlpError::AlreadyUndelegated.into()); + } } - - // Record whether this commit requested undelegation. - delegation_metadata.undelegation_requester = - UndelegationRequester::from_allow_undelegation(args.allow_undelegation); delegation_metadata .to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut()) .map_err(to_pinocchio_program_error)?; diff --git a/src/processor/fast/finalize.rs b/src/processor/fast/finalize.rs index beb364f4..6013f537 100644 --- a/src/processor/fast/finalize.rs +++ b/src/processor/fast/finalize.rs @@ -8,7 +8,14 @@ use pinocchio_log::log; use super::to_pinocchio_program_error; use crate::{ error::DlpError, - processor::fast::utils::pda::close_pda, + processor::fast::{ + internal::{ + parse_auto_undelegation_accounts, + process_auto_undelegation_if_requested, + }, + utils::pda::close_pda, + }, + require_n_accounts_with_optionals, requires::{ is_uninitialized_account, require_initialized_commit_record, require_initialized_commit_state, @@ -58,11 +65,11 @@ pub fn process_finalize( accounts: &[AccountView], _data: &[u8], ) -> ProgramResult { - let [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, _system_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; + let ( + [validator, delegated_account, commit_state_account, commit_record_account, delegation_record_account, delegation_metadata_account, validator_fees_vault, system_program], + optional_accounts, + ) = require_n_accounts_with_optionals!(accounts, 8); + let auto_accounts = parse_auto_undelegation_accounts(optional_accounts)?; require_signer(validator, "validator")?; require_owned_pda( @@ -162,6 +169,7 @@ pub fn process_finalize( // Update the delegation metadata delegation_metadata.last_commit_id = commit_record.nonce; + let undelegation_requester = delegation_metadata.undelegation_requester; delegation_metadata .to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut()) .map_err(to_pinocchio_program_error)?; @@ -182,6 +190,21 @@ pub fn process_finalize( drop(commit_record_data); drop(commit_state_data); + drop(delegated_account_data); + drop(delegation_record_data); + drop(delegation_metadata_data); + + process_auto_undelegation_if_requested( + undelegation_requester, + validator, + delegated_account, + delegation_record_account, + delegation_metadata_account, + validator_fees_vault, + system_program, + auto_accounts, + )?; + // Closing accounts close_pda(commit_state_account, validator)?; close_pda(commit_record_account, validator)?; diff --git a/src/processor/fast/internal/commit_finalize_internal.rs b/src/processor/fast/internal/commit_finalize_internal.rs index 8a13c201..17e616b1 100644 --- a/src/processor/fast/internal/commit_finalize_internal.rs +++ b/src/processor/fast/internal/commit_finalize_internal.rs @@ -35,7 +35,7 @@ pub(crate) struct CommitFinalizeInternalArgs<'a> { /// Commit a new state of a delegated Pda pub(crate) fn process_commit_finalize_internal( args: CommitFinalizeInternalArgs, -) -> Result<(), ProgramError> { +) -> Result { // check delegated_account is actually delegated to the DLP require_owned_by!(args.delegated_account, &crate::fast::ID); @@ -78,7 +78,7 @@ pub(crate) fn process_commit_finalize_internal( ); // validate and update metadata - { + let undelegation_requester = { let mut metadata = DelegationMetadataFast::from_account( args.delegation_metadata_account, )?; @@ -87,16 +87,22 @@ pub(crate) fn process_commit_finalize_internal( require_eq!(args.commit_id, prev_id + 1, DlpError::NonceOutOfOrder); - let previous_requester = metadata.replace_undelegation_requester( - UndelegationRequester::from_allow_undelegation( - args.allow_undelegation, - ), - )?; - require!( - previous_requester != UndelegationRequester::Validator, - DlpError::AlreadyUndelegated - ); - } + match metadata.undelegation_requester()? { + UndelegationRequester::None => { + let requester = UndelegationRequester::from_allow_undelegation( + args.allow_undelegation, + ); + metadata.set_undelegation_requester(requester); + requester + } + UndelegationRequester::OwnerProgram => { + UndelegationRequester::OwnerProgram + } + UndelegationRequester::Validator => { + return Err(DlpError::AlreadyUndelegated.into()); + } + } + }; let mut delegation_record_data = args.delegation_record_account.try_borrow_mut()?; @@ -169,5 +175,5 @@ pub(crate) fn process_commit_finalize_internal( } } - Ok(()) + Ok(undelegation_requester) } diff --git a/src/processor/fast/internal/mod.rs b/src/processor/fast/internal/mod.rs index 03bcdc23..22f6ba25 100644 --- a/src/processor/fast/internal/mod.rs +++ b/src/processor/fast/internal/mod.rs @@ -1,3 +1,5 @@ mod commit_finalize_internal; +mod undelegate_internal; pub(crate) use commit_finalize_internal::*; +pub(crate) use undelegate_internal::*; diff --git a/src/processor/fast/internal/undelegate_internal.rs b/src/processor/fast/internal/undelegate_internal.rs new file mode 100644 index 00000000..dd4e9c28 --- /dev/null +++ b/src/processor/fast/internal/undelegate_internal.rs @@ -0,0 +1,485 @@ +use dlp_api::compat::borsh; +use pinocchio::{ + address::{address_eq, Address}, + cpi::{invoke_signed, Signer}, + error::ProgramError, + instruction::{seeds, InstructionAccount, InstructionView}, + sysvars::{rent::Rent, Sysvar}, + AccountView, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_system::instructions as system; + +use crate::{ + consts::{ + COMMIT_FEE_LAMPORTS, EXTERNAL_UNDELEGATE_DISCRIMINATOR, + SESSION_FEE_LAMPORTS, + }, + error::DlpError, + pda, + processor::fast::{ + to_pinocchio_program_error, + utils::pda::{close_pda, close_pda_with_fees, create_pda}, + }, + require_eq_keys, + requires::{ + require_initialized_delegation_metadata, + require_initialized_delegation_record, require_initialized_pda, + require_initialized_protocol_fees_vault, + require_initialized_validator_fees_vault, require_owned_pda, + require_uninitialized_pda, UndelegateBufferCtx, + }, + state::{ + DelegationMetadata, DelegationRecord, UndelegationRequest, + UndelegationRequester, + }, +}; + +pub(crate) struct AutoUndelegationAccounts<'a> { + pub(crate) owner_program: &'a AccountView, + pub(crate) undelegate_buffer_account: &'a AccountView, + pub(crate) rent_reimbursement: &'a AccountView, + pub(crate) fees_vault: &'a AccountView, + pub(crate) undelegation_request_account: &'a AccountView, +} + +pub(crate) fn parse_auto_undelegation_accounts( + accounts: &[AccountView], +) -> Result>, ProgramError> { + match accounts.len() { + 0 => Ok(None), + 5 => { + let [owner_program, undelegate_buffer_account, rent_reimbursement, fees_vault, undelegation_request_account] = + TryInto::<&[_; 5]>::try_into(accounts) + .map_err(|_| DlpError::InfallibleError)?; + Ok(Some(AutoUndelegationAccounts { + owner_program, + undelegate_buffer_account, + rent_reimbursement, + fees_vault, + undelegation_request_account, + })) + } + _ => Err(ProgramError::InvalidInstructionData), + } +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn process_auto_undelegation_if_requested( + requester: UndelegationRequester, + validator: &AccountView, + delegated_account: &AccountView, + delegation_record_account: &AccountView, + delegation_metadata_account: &AccountView, + validator_fees_vault: &AccountView, + system_program: &AccountView, + auto_accounts: Option>, +) -> ProgramResult { + if requester != UndelegationRequester::OwnerProgram { + return Ok(()); + } + + let auto_accounts = + auto_accounts.ok_or(ProgramError::NotEnoughAccountKeys)?; + process_undelegation(UndelegationAccounts { + validator, + delegated_account, + owner_program: auto_accounts.owner_program, + undelegate_buffer_account: auto_accounts.undelegate_buffer_account, + delegation_record_account, + delegation_metadata_account, + rent_reimbursement: auto_accounts.rent_reimbursement, + fees_vault: auto_accounts.fees_vault, + validator_fees_vault, + system_program, + // See UndelegationRequest::rent_payer for this invariant. + request_accounts: Some(( + auto_accounts.undelegation_request_account, + auto_accounts.rent_reimbursement, + )), + }) +} + +pub(crate) struct UndelegationAccounts<'a> { + pub(crate) validator: &'a AccountView, + pub(crate) delegated_account: &'a AccountView, + pub(crate) owner_program: &'a AccountView, + pub(crate) undelegate_buffer_account: &'a AccountView, + pub(crate) delegation_record_account: &'a AccountView, + pub(crate) delegation_metadata_account: &'a AccountView, + pub(crate) rent_reimbursement: &'a AccountView, + pub(crate) fees_vault: &'a AccountView, + pub(crate) validator_fees_vault: &'a AccountView, + pub(crate) system_program: &'a AccountView, + pub(crate) request_accounts: Option<(&'a AccountView, &'a AccountView)>, +} + +pub(crate) fn process_undelegation( + accounts: UndelegationAccounts<'_>, +) -> ProgramResult { + if let Some((undelegation_request_account, request_rent_payer)) = + accounts.request_accounts + { + require_valid_undelegation_request( + accounts.delegated_account, + accounts.owner_program, + undelegation_request_account, + request_rent_payer, + )?; + }; + + require_owned_pda( + accounts.delegated_account, + &crate::fast::ID, + "delegated account", + )?; + require_initialized_delegation_record( + accounts.delegated_account, + accounts.delegation_record_account, + true, + )?; + require_initialized_delegation_metadata( + accounts.delegated_account, + accounts.delegation_metadata_account, + true, + )?; + require_initialized_protocol_fees_vault(accounts.fees_vault, true)?; + require_initialized_validator_fees_vault( + accounts.validator, + accounts.validator_fees_vault, + true, + )?; + + // Load delegation record + let delegation_record_data = + accounts.delegation_record_account.try_borrow()?; + let delegation_record = + DelegationRecord::try_from_bytes_with_discriminator( + &delegation_record_data, + ) + .map_err(to_pinocchio_program_error)?; + + // Check passed owner and owner stored in the delegation record match + if !address_eq( + &delegation_record.owner.to_bytes().into(), + accounts.owner_program.address(), + ) { + log!("Expected delegation record owner to be : "); + Address::from(delegation_record.owner.to_bytes()).log(); + log!("but got : "); + accounts.owner_program.address().log(); + return Err(ProgramError::InvalidAccountOwner); + } + + // Load delegated account metadata + let delegation_metadata_data = + accounts.delegation_metadata_account.try_borrow()?; + let delegation_metadata = + DelegationMetadata::try_from_bytes_with_discriminator( + &delegation_metadata_data, + ) + .map_err(to_pinocchio_program_error)?; + let delegation_last_commit_id = delegation_metadata.last_commit_id; + + // Check if undelegation has been requested for the delegated account. + if delegation_metadata.undelegation_requester == UndelegationRequester::None + { + log!("delegation metadata has no undelegation requester: "); + accounts.delegation_metadata_account.address().log(); + return Err(DlpError::NotUndelegatable.into()); + } + + // Check if the rent payer is correct + if !address_eq( + &delegation_metadata.rent_payer.to_bytes().into(), + accounts.rent_reimbursement.address(), + ) { + log!("Expected rent payer to be : "); + Address::from(delegation_metadata.rent_payer.to_bytes()).log(); + log!("but got : "); + accounts.rent_reimbursement.address().log(); + return Err( + DlpError::InvalidReimbursementAddressForDelegationRent.into() + ); + } + if let Some((_undelegation_request_account, request_rent_payer)) = + accounts.request_accounts + { + require_eq_keys!( + request_rent_payer.address(), + accounts.rent_reimbursement.address(), + DlpError::InvalidUndelegationRequest + ); + } + + // Dropping delegation references + drop(delegation_record_data); + drop(delegation_metadata_data); + + // If there is no data, we can just assign the owner back and we're done + if accounts.delegated_account.is_data_empty() { + // TODO - we could also do this fast-path if the data was non-empty but zeroed-out + unsafe { + accounts + .delegated_account + .assign(accounts.owner_program.address()); + } + process_delegation_cleanup( + accounts.delegation_record_account, + accounts.delegation_metadata_account, + accounts.rent_reimbursement, + accounts.fees_vault, + accounts.validator_fees_vault, + delegation_last_commit_id, + )?; + if let Some((undelegation_request_account, request_rent_payer)) = + accounts.request_accounts + { + close_pda(undelegation_request_account, request_rent_payer)?; + } + return Ok(()); + } + + // Initialize the undelegation buffer PDA + let undelegate_buffer_bump: u8 = require_uninitialized_pda( + accounts.undelegate_buffer_account, + &[ + pda::UNDELEGATE_BUFFER_TAG, + accounts.delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + UndelegateBufferCtx, + )?; + + create_pda( + accounts.undelegate_buffer_account, + &crate::fast::ID, + accounts.delegated_account.data_len(), + &[Signer::from(&seeds!( + pda::UNDELEGATE_BUFFER_TAG, + accounts.delegated_account.address().as_ref(), + &[undelegate_buffer_bump] + ))], + accounts.validator, + )?; + + // Copy data in the undelegation buffer PDA + (*accounts.undelegate_buffer_account.try_borrow_mut()?) + .copy_from_slice(&accounts.delegated_account.try_borrow()?); + + // Call a CPI to the owner program to give it back the new state + process_undelegation_with_cpi( + accounts.validator, + accounts.delegated_account, + accounts.owner_program, + accounts.undelegate_buffer_account, + &[Signer::from(&seeds!( + pda::UNDELEGATE_BUFFER_TAG, + accounts.delegated_account.address().as_ref(), + &[undelegate_buffer_bump] + ))], + delegation_metadata, + accounts.system_program, + )?; + + // Done, close undelegation buffer + close_pda(accounts.undelegate_buffer_account, accounts.validator)?; + + // Closing delegation accounts + process_delegation_cleanup( + accounts.delegation_record_account, + accounts.delegation_metadata_account, + accounts.rent_reimbursement, + accounts.fees_vault, + accounts.validator_fees_vault, + delegation_last_commit_id, + )?; + if let Some((undelegation_request_account, request_rent_payer)) = + accounts.request_accounts + { + close_pda(undelegation_request_account, request_rent_payer)?; + } + Ok(()) +} + +fn require_valid_undelegation_request( + delegated_account: &AccountView, + owner_program: &AccountView, + undelegation_request_account: &AccountView, + request_rent_payer: &AccountView, +) -> ProgramResult { + require_initialized_pda( + undelegation_request_account, + &[ + pda::UNDELEGATION_REQUEST_TAG, + delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + "undelegation request", + )?; + + let request_data = undelegation_request_account.try_borrow()?; + let request = + UndelegationRequest::try_from_bytes_with_discriminator(&request_data) + .map_err(to_pinocchio_program_error)?; + + if !address_eq( + &request.delegated_account.to_bytes().into(), + delegated_account.address(), + ) || !address_eq( + &request.owner_program.to_bytes().into(), + owner_program.address(), + ) || !address_eq( + &request.rent_payer.to_bytes().into(), + request_rent_payer.address(), + ) { + return Err(DlpError::InvalidUndelegationRequest.into()); + } + + Ok(()) +} + +/// 1. Close the delegated account +/// 2. CPI to the owner program +/// 3. Check state +/// 4. Settle lamports balance +#[allow(clippy::too_many_arguments)] +pub(crate) fn process_undelegation_with_cpi( + validator: &AccountView, + delegated_account: &AccountView, + owner_program: &AccountView, + undelegate_buffer_account: &AccountView, + undelegate_buffer_signer_seeds: &[Signer], + delegation_metadata: DelegationMetadata, + system_program: &AccountView, +) -> ProgramResult { + let delegated_account_lamports_before_close = delegated_account.lamports(); + close_pda(delegated_account, validator)?; + + // Invoke the owner program's post-undelegation IX, to give the state back to the original program + let validator_lamports_before_cpi = validator.lamports(); + + cpi_external_undelegate( + validator, + delegated_account, + undelegate_buffer_account, + undelegate_buffer_signer_seeds, + system_program, + owner_program.address(), + delegation_metadata, + )?; + + let validator_lamports_after_cpi = validator.lamports(); + + // Check that the validator lamports are exactly as expected + let delegated_account_min_rent = + Rent::get()?.try_minimum_balance(delegated_account.data_len())?; + if validator_lamports_before_cpi + != validator_lamports_after_cpi + .checked_add(delegated_account_min_rent) + .ok_or(DlpError::Overflow)? + { + return Err(DlpError::InvalidValidatorBalanceAfterCPI.into()); + } + + // Check that the owner program properly moved the state back into the original account during CPI + if delegated_account.try_borrow()?.as_ref() + != undelegate_buffer_account.try_borrow()?.as_ref() + { + return Err(DlpError::InvalidAccountDataAfterCPI.into()); + } + + // Return the extra lamports to the delegated account + let delegated_account_extra_lamports = + delegated_account_lamports_before_close + .checked_sub(delegated_account_min_rent) + .ok_or(DlpError::Overflow)?; + + system::Transfer { + from: validator, + to: delegated_account, + lamports: delegated_account_extra_lamports, + } + .invoke()?; + Ok(()) +} + +/// CPI to the original owner program to re-open the PDA with the new state +fn cpi_external_undelegate( + payer: &AccountView, + delegated_account: &AccountView, + undelegate_buffer_account: &AccountView, + undelegate_buffer_signer_seeds: &[Signer], + system_program: &AccountView, + owner_program_id: &Address, + delegation_metadata: DelegationMetadata, +) -> ProgramResult { + let data = { + let mut data = Vec::with_capacity(32); + data.extend_from_slice(&EXTERNAL_UNDELEGATE_DISCRIMINATOR); + borsh::to_writer(&mut data, &delegation_metadata.seeds) + .map_err(|_| ProgramError::BorshIoError)?; + data + }; + + let external_undelegate_instruction = InstructionView { + program_id: owner_program_id, + data: &data, + accounts: &[ + InstructionAccount::new(delegated_account.address(), true, false), + InstructionAccount::new( + undelegate_buffer_account.address(), + true, + true, + ), + InstructionAccount::new(payer.address(), true, true), + InstructionAccount::new(system_program.address(), false, false), + ], + }; + + invoke_signed( + &external_undelegate_instruction, + &[ + delegated_account, + undelegate_buffer_account, + payer, + system_program, + ], + undelegate_buffer_signer_seeds, + ) +} + +fn process_delegation_cleanup( + delegation_record_account: &AccountView, + delegation_metadata_account: &AccountView, + rent_reimbursement: &AccountView, + fees_vault: &AccountView, + validator_fees_vault: &AccountView, + delegation_last_commit_id: u64, +) -> ProgramResult { + let commit_count = delegation_last_commit_id.saturating_sub(1); + let commit_fee = COMMIT_FEE_LAMPORTS + .checked_mul(commit_count) + .ok_or(DlpError::Overflow)?; + let total_fee_requested = commit_fee + SESSION_FEE_LAMPORTS; + let total_lamports = delegation_record_account.lamports() + + delegation_metadata_account.lamports(); + let mut fee_remaining = total_fee_requested.min(total_lamports); + close_pda_with_fees( + delegation_record_account, + rent_reimbursement, + fees_vault, + validator_fees_vault, + &mut fee_remaining, + )?; + close_pda_with_fees( + delegation_metadata_account, + rent_reimbursement, + fees_vault, + validator_fees_vault, + &mut fee_remaining, + )?; + Ok(()) +} diff --git a/src/processor/fast/request_undelegation.rs b/src/processor/fast/request_undelegation.rs index cd81c946..13781c29 100644 --- a/src/processor/fast/request_undelegation.rs +++ b/src/processor/fast/request_undelegation.rs @@ -20,7 +20,7 @@ use crate::{ require_signer, require_uninitialized_pda, UndelegationRequestCtx, }, state::{ - DelegationMetadataFast, DelegationRecord, UndelegationRequest, + DelegationMetadata, DelegationRecord, UndelegationRequest, UndelegationRequester, }, }; @@ -54,7 +54,6 @@ pub fn process_request_undelegation( require!(data.is_empty(), ProgramError::InvalidInstructionData); require_signer(payer, "payer")?; - require!(payer.is_writable(), ProgramError::Immutable); require_signer(delegated_account, "delegated account")?; require_owned_pda( delegated_account, @@ -91,6 +90,20 @@ pub fn process_request_undelegation( ProgramError::InvalidAccountOwner ); + let mut delegation_metadata_data = + delegation_metadata_account.try_borrow_mut()?; + let mut delegation_metadata = + DelegationMetadata::try_from_bytes_with_discriminator( + &delegation_metadata_data, + ) + .map_err(to_pinocchio_program_error)?; + // See UndelegationRequest::rent_payer for this invariant. + require_eq_keys!( + &delegation_metadata.rent_payer, + payer.address(), + DlpError::InvalidReimbursementAddressForDelegationRent + ); + let request_seeds = &[ pda::UNDELEGATION_REQUEST_TAG, delegated_account.address().as_ref(), @@ -121,11 +134,12 @@ pub fn process_request_undelegation( payer, )?; - let mut delegation_metadata = - DelegationMetadataFast::from_account(delegation_metadata_account)?; + delegation_metadata.undelegation_requester = + UndelegationRequester::OwnerProgram; + let last_commit_id_at_request = delegation_metadata.last_commit_id; delegation_metadata - .set_undelegation_requester(UndelegationRequester::OwnerProgram); - let last_commit_id_at_request = delegation_metadata.last_commit_id(); + .to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut()) + .map_err(to_pinocchio_program_error)?; let request = UndelegationRequest { delegated_account: *delegated_account.address(), diff --git a/src/processor/fast/undelegate.rs b/src/processor/fast/undelegate.rs index edb2ea8c..e8837a2b 100644 --- a/src/processor/fast/undelegate.rs +++ b/src/processor/fast/undelegate.rs @@ -1,38 +1,22 @@ -use dlp_api::compat::borsh; use pinocchio::{ address::{address_eq, Address}, - cpi::{invoke_signed, Signer}, error::ProgramError, - instruction::{seeds, InstructionAccount, InstructionView}, - sysvars::{rent::Rent, Sysvar}, AccountView, ProgramResult, }; -use pinocchio_log::log; -use pinocchio_system::instructions as system; -use super::to_pinocchio_program_error; #[cfg(feature = "log-cost")] use crate::compute; use crate::{ - consts::{ - COMMIT_FEE_LAMPORTS, EXTERNAL_UNDELEGATE_DISCRIMINATOR, - SESSION_FEE_LAMPORTS, - }, - error::DlpError, pda, - processor::fast::utils::pda::{close_pda, close_pda_with_fees, create_pda}, - require_n_accounts, require_n_accounts_with_optionals, + processor::fast::internal::{process_undelegation, UndelegationAccounts}, + require_eq_keys, require_n_accounts, require_n_accounts_with_optionals, requires::{ require_initialized_delegation_metadata, - require_initialized_delegation_record, require_initialized_pda, + require_initialized_delegation_record, require_initialized_protocol_fees_vault, require_initialized_validator_fees_vault, require_owned_pda, require_signer, require_uninitialized_pda, CommitRecordCtx, - CommitStateAccountCtx, UndelegateBufferCtx, - }, - state::{ - DelegationMetadata, DelegationRecord, UndelegationRequest, - UndelegationRequester, + CommitStateAccountCtx, }, }; @@ -113,16 +97,17 @@ pub fn process_undelegate( _ => return Err(ProgramError::InvalidInstructionData), }; - if let Some((undelegation_request_account, request_rent_payer)) = - request_accounts - { - require_valid_undelegation_request( - delegated_account, - owner_program, - undelegation_request_account, - request_rent_payer, - )?; - }; + let delegated_account_owner = unsafe { delegated_account.owner() }; + if !address_eq(delegated_account_owner, &crate::fast::ID) { + // this case is possible only if one of the previous instruction (Finalize or + // CommitFinalize) has undelegated the account. + require_eq_keys!( + delegated_account_owner, + owner_program.address(), + ProgramError::InvalidAccountOwner + ); + return Ok(()); + } // Check accounts require_signer(validator, "validator")?; @@ -164,327 +149,17 @@ pub fn process_undelegate( CommitRecordCtx, )?; - // Load delegation record - let delegation_record_data = delegation_record_account.try_borrow()?; - let delegation_record = - DelegationRecord::try_from_bytes_with_discriminator( - &delegation_record_data, - ) - .map_err(to_pinocchio_program_error)?; - - // Check passed owner and owner stored in the delegation record match - if !address_eq( - &delegation_record.owner.to_bytes().into(), - owner_program.address(), - ) { - log!("Expected delegation record owner to be : "); - Address::from(delegation_record.owner.to_bytes()).log(); - log!("but got : "); - owner_program.address().log(); - return Err(ProgramError::InvalidAccountOwner); - } - - // Load delegated account metadata - let delegation_metadata_data = delegation_metadata_account.try_borrow()?; - let delegation_metadata = - DelegationMetadata::try_from_bytes_with_discriminator( - &delegation_metadata_data, - ) - .map_err(to_pinocchio_program_error)?; - let delegation_last_commit_id = delegation_metadata.last_commit_id; - - // Check if undelegation has been requested for the delegated account. - if delegation_metadata.undelegation_requester == UndelegationRequester::None - { - log!("delegation metadata has no undelegation requester: "); - delegation_metadata_account.address().log(); - return Err(DlpError::NotUndelegatable.into()); - } - - // Check if the rent payer is correct - if !address_eq( - &delegation_metadata.rent_payer.to_bytes().into(), - rent_reimbursement.address(), - ) { - log!("Expected rent payer to be : "); - Address::from(delegation_metadata.rent_payer.to_bytes()).log(); - log!("but got : "); - rent_reimbursement.address().log(); - return Err( - DlpError::InvalidReimbursementAddressForDelegationRent.into() - ); - } - - // Dropping delegation references - drop(delegation_record_data); - drop(delegation_metadata_data); - - // If there is no data, we can just assign the owner back and we're done - if delegated_account.is_data_empty() { - // TODO - we could also do this fast-path if the data was non-empty but zeroed-out - unsafe { - delegated_account.assign(owner_program.address()); - } - process_delegation_cleanup( - delegation_record_account, - delegation_metadata_account, - rent_reimbursement, - fees_vault, - validator_fees_vault, - delegation_last_commit_id, - )?; - if let Some((undelegation_request_account, request_rent_payer)) = - request_accounts - { - close_pda(undelegation_request_account, request_rent_payer)?; - } - return Ok(()); - } - - // Initialize the undelegation buffer PDA - let undelegate_buffer_bump: u8 = require_uninitialized_pda( - undelegate_buffer_account, - &[ - pda::UNDELEGATE_BUFFER_TAG, - delegated_account.address().as_ref(), - ], - &crate::fast::ID, - true, - UndelegateBufferCtx, - )?; - - create_pda( - undelegate_buffer_account, - &crate::fast::ID, - delegated_account.data_len(), - &[Signer::from(&seeds!( - pda::UNDELEGATE_BUFFER_TAG, - delegated_account.address().as_ref(), - &[undelegate_buffer_bump] - ))], - validator, - )?; - - // Copy data in the undelegation buffer PDA - (*undelegate_buffer_account.try_borrow_mut()?) - .copy_from_slice(&delegated_account.try_borrow()?); - - // Call a CPI to the owner program to give it back the new state - process_undelegation_with_cpi( + process_undelegation(UndelegationAccounts { validator, delegated_account, owner_program, undelegate_buffer_account, - &[Signer::from(&seeds!( - pda::UNDELEGATE_BUFFER_TAG, - delegated_account.address().as_ref(), - &[undelegate_buffer_bump] - ))], - delegation_metadata, - system_program, - )?; - - // Done, close undelegation buffer - close_pda(undelegate_buffer_account, validator)?; - - // Closing delegation accounts - process_delegation_cleanup( delegation_record_account, delegation_metadata_account, rent_reimbursement, fees_vault, validator_fees_vault, - delegation_last_commit_id, - )?; - if let Some((undelegation_request_account, request_rent_payer)) = - request_accounts - { - close_pda(undelegation_request_account, request_rent_payer)?; - } - Ok(()) -} - -fn require_valid_undelegation_request( - delegated_account: &AccountView, - owner_program: &AccountView, - undelegation_request_account: &AccountView, - request_rent_payer: &AccountView, -) -> ProgramResult { - require_initialized_pda( - undelegation_request_account, - &[ - pda::UNDELEGATION_REQUEST_TAG, - delegated_account.address().as_ref(), - ], - &crate::fast::ID, - true, - "undelegation request", - )?; - - if !request_rent_payer.is_writable() { - return Err(ProgramError::Immutable); - } - - let request_data = undelegation_request_account.try_borrow()?; - let request = - UndelegationRequest::try_from_bytes_with_discriminator(&request_data) - .map_err(to_pinocchio_program_error)?; - - if !address_eq( - &request.delegated_account.to_bytes().into(), - delegated_account.address(), - ) || !address_eq( - &request.owner_program.to_bytes().into(), - owner_program.address(), - ) || !address_eq( - &request.rent_payer.to_bytes().into(), - request_rent_payer.address(), - ) { - return Err(DlpError::InvalidUndelegationRequest.into()); - } - - Ok(()) -} - -/// 1. Close the delegated account -/// 2. CPI to the owner program -/// 3. Check state -/// 4. Settle lamports balance -#[allow(clippy::too_many_arguments)] -pub(crate) fn process_undelegation_with_cpi( - validator: &AccountView, - delegated_account: &AccountView, - owner_program: &AccountView, - undelegate_buffer_account: &AccountView, - undelegate_buffer_signer_seeds: &[Signer], - delegation_metadata: DelegationMetadata, - system_program: &AccountView, -) -> ProgramResult { - let delegated_account_lamports_before_close = delegated_account.lamports(); - close_pda(delegated_account, validator)?; - - // Invoke the owner program's post-undelegation IX, to give the state back to the original program - let validator_lamports_before_cpi = validator.lamports(); - - cpi_external_undelegate( - validator, - delegated_account, - undelegate_buffer_account, - undelegate_buffer_signer_seeds, system_program, - owner_program.address(), - delegation_metadata, - )?; - - let validator_lamports_after_cpi = validator.lamports(); - - // Check that the validator lamports are exactly as expected - let delegated_account_min_rent = - Rent::get()?.try_minimum_balance(delegated_account.data_len())?; - if validator_lamports_before_cpi - != validator_lamports_after_cpi - .checked_add(delegated_account_min_rent) - .ok_or(DlpError::Overflow)? - { - return Err(DlpError::InvalidValidatorBalanceAfterCPI.into()); - } - - // Check that the owner program properly moved the state back into the original account during CPI - if delegated_account.try_borrow()?.as_ref() - != undelegate_buffer_account.try_borrow()?.as_ref() - { - return Err(DlpError::InvalidAccountDataAfterCPI.into()); - } - - // Return the extra lamports to the delegated account - let delegated_account_extra_lamports = - delegated_account_lamports_before_close - .checked_sub(delegated_account_min_rent) - .ok_or(DlpError::Overflow)?; - - system::Transfer { - from: validator, - to: delegated_account, - lamports: delegated_account_extra_lamports, - } - .invoke()?; - Ok(()) -} - -/// CPI to the original owner program to re-open the PDA with the new state -fn cpi_external_undelegate( - payer: &AccountView, - delegated_account: &AccountView, - undelegate_buffer_account: &AccountView, - undelegate_buffer_signer_seeds: &[Signer], - system_program: &AccountView, - owner_program_id: &Address, - delegation_metadata: DelegationMetadata, -) -> ProgramResult { - let data = { - let mut data = Vec::with_capacity(32); - data.extend_from_slice(&EXTERNAL_UNDELEGATE_DISCRIMINATOR); - borsh::to_writer(&mut data, &delegation_metadata.seeds) - .map_err(|_| ProgramError::BorshIoError)?; - data - }; - - let external_undelegate_instruction = InstructionView { - program_id: owner_program_id, - data: &data, - accounts: &[ - InstructionAccount::new(delegated_account.address(), true, false), - InstructionAccount::new( - undelegate_buffer_account.address(), - true, - true, - ), - InstructionAccount::new(payer.address(), true, true), - InstructionAccount::new(system_program.address(), false, false), - ], - }; - - invoke_signed( - &external_undelegate_instruction, - &[ - delegated_account, - undelegate_buffer_account, - payer, - system_program, - ], - undelegate_buffer_signer_seeds, - ) -} - -fn process_delegation_cleanup( - delegation_record_account: &AccountView, - delegation_metadata_account: &AccountView, - rent_reimbursement: &AccountView, - fees_vault: &AccountView, - validator_fees_vault: &AccountView, - delegation_last_commit_id: u64, -) -> ProgramResult { - let commit_count = delegation_last_commit_id.saturating_sub(1); - let commit_fee = COMMIT_FEE_LAMPORTS - .checked_mul(commit_count) - .ok_or(DlpError::Overflow)?; - let total_fee_requested = commit_fee + SESSION_FEE_LAMPORTS; - let total_lamports = delegation_record_account.lamports() - + delegation_metadata_account.lamports(); - let mut fee_remaining = total_fee_requested.min(total_lamports); - close_pda_with_fees( - delegation_record_account, - rent_reimbursement, - fees_vault, - validator_fees_vault, - &mut fee_remaining, - )?; - close_pda_with_fees( - delegation_metadata_account, - rent_reimbursement, - fees_vault, - validator_fees_vault, - &mut fee_remaining, - )?; - Ok(()) + request_accounts, + }) } diff --git a/src/processor/fast/undelegate_confined_account.rs b/src/processor/fast/undelegate_confined_account.rs index 52879fe9..cac2fc7b 100644 --- a/src/processor/fast/undelegate_confined_account.rs +++ b/src/processor/fast/undelegate_confined_account.rs @@ -3,11 +3,14 @@ use pinocchio::{ ProgramResult, }; -use super::{process_undelegation_with_cpi, to_pinocchio_program_error}; +use super::to_pinocchio_program_error; use crate::{ error::DlpError, pda, - processor::fast::utils::pda::{close_pda, create_pda}, + processor::fast::{ + internal::process_undelegation_with_cpi, + utils::pda::{close_pda, create_pda}, + }, require_eq_keys, requires::{ require_authorization, require_initialized_delegation_metadata, diff --git a/tests/integration/tests/test-delegation.ts b/tests/integration/tests/test-delegation.ts index acb2724f..d2a1a7bc 100644 --- a/tests/integration/tests/test-delegation.ts +++ b/tests/integration/tests/test-delegation.ts @@ -365,7 +365,7 @@ describe("TestDelegation", () => { assert.isAtMost( parseInt(consumedLog.split(" ").at(3)), - 45000, + 55000, "undelegate instruction must consume less than 18500" ); }); diff --git a/tests/test_call_handler.rs b/tests/test_call_handler.rs index 23d188c4..f05e832b 100644 --- a/tests/test_call_handler.rs +++ b/tests/test_call_handler.rs @@ -339,6 +339,8 @@ async fn test_finalize_call_handler() { let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + validator.pubkey(), ); let call_handler_ix = dlp_api::instruction_builder::call_handler( validator.pubkey(), @@ -387,6 +389,8 @@ async fn test_undelegate_call_handler() { let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + validator.pubkey(), ); let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), @@ -454,6 +458,8 @@ async fn test_finalize_invalid_escrow_call_handler() { let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); let call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), @@ -489,6 +495,8 @@ async fn test_undelegate_invalid_escow_call_handler() { let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); let finalize_call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), diff --git a/tests/test_call_handler_v2.rs b/tests/test_call_handler_v2.rs index 716b01c1..c200df7e 100644 --- a/tests/test_call_handler_v2.rs +++ b/tests/test_call_handler_v2.rs @@ -333,6 +333,8 @@ async fn test_finalize_call_handler_v2() { let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + validator.pubkey(), ); let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( validator.pubkey(), @@ -382,6 +384,8 @@ async fn test_undelegate_call_handler_v2() { let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + validator.pubkey(), ); let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), @@ -448,6 +452,8 @@ async fn test_finalize_invalid_escrow_call_handler_v2() { let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( authority.pubkey(), @@ -484,6 +490,8 @@ async fn test_undelegate_invalid_escrow_call_handler_v2() { let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); let finalize_call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( diff --git a/tests/test_commit_finalize.rs b/tests/test_commit_finalize.rs index 3f78ecf8..8a552836 100644 --- a/tests/test_commit_finalize.rs +++ b/tests/test_commit_finalize.rs @@ -26,19 +26,19 @@ use solana_sdk_ids::system_program; use crate::fixtures::{ get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, - TEST_AUTHORITY, + DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, }; mod fixtures; #[tokio::test] async fn test_commit_finalize_data_perf() { - run_test_commit_finalize(vec![0; 10240], vec![1; 10240], false, 1400).await; + run_test_commit_finalize(vec![0; 10240], vec![1; 10240], false, 1550).await; } #[tokio::test] async fn test_commit_finalize_diff_perf() { - run_test_commit_finalize(vec![0; 10240], vec![1; 10240], true, 1650).await; + run_test_commit_finalize(vec![0; 10240], vec![1; 10240], true, 1800).await; } async fn run_test_commit_finalize( @@ -57,6 +57,8 @@ async fn run_test_commit_finalize( let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 1, allow_undelegation: true.into(), @@ -133,6 +135,8 @@ async fn test_commit_finalize_out_of_order() { let (ix, _pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 2, // this is the min value which will cause NonceOutOfOrder allow_undelegation: true.into(), @@ -285,6 +289,8 @@ async fn test_commit_finalize_lamports_increase() { let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 1, allow_undelegation: false.into(), @@ -363,6 +369,8 @@ async fn test_commit_finalize_lamports_decrease() { let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 1, allow_undelegation: false.into(), @@ -426,6 +434,8 @@ async fn test_commit_finalize_rejects_underfunded_account() { let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 1, allow_undelegation: false.into(), diff --git a/tests/test_commit_finalize_from_buffer.rs b/tests/test_commit_finalize_from_buffer.rs index 5c861190..53434dfb 100644 --- a/tests/test_commit_finalize_from_buffer.rs +++ b/tests/test_commit_finalize_from_buffer.rs @@ -22,7 +22,7 @@ use solana_sdk_ids::system_program; use crate::fixtures::{ get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID, - TEST_AUTHORITY, + DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, }; mod fixtures; @@ -43,6 +43,8 @@ async fn test_commit_finalize_from_buffer_perf() { authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 1, allow_undelegation: true.into(), @@ -68,7 +70,7 @@ async fn test_commit_finalize_from_buffer_perf() { let metadata = metadata.unwrap(); - assertables::assert_lt!(metadata.compute_units_consumed, 1450); + assertables::assert_lt!(metadata.compute_units_consumed, 1600); assert_eq!( metadata.log_messages.len(), @@ -114,6 +116,8 @@ async fn test_commit_finalize_from_buffer_out_of_order() { authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), &mut CommitFinalizeArgs { commit_id: 2, // this is the min value which will cause NonceOutOfOrder allow_undelegation: true.into(), diff --git a/tests/test_commit_undelegate_zero_lamports_system_owned.rs b/tests/test_commit_undelegate_zero_lamports_system_owned.rs index abe14bd9..78932af9 100644 --- a/tests/test_commit_undelegate_zero_lamports_system_owned.rs +++ b/tests/test_commit_undelegate_zero_lamports_system_owned.rs @@ -62,6 +62,8 @@ async fn test_commit_and_undelegate_zero_lamports_system_owned_account() { let ix_finalize = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + validator.pubkey(), ); let ix_undelegate = dlp_api::instruction_builder::undelegate( validator.pubkey(), diff --git a/tests/test_finalize.rs b/tests/test_finalize.rs index 030141e8..a8a516d1 100644 --- a/tests/test_finalize.rs +++ b/tests/test_finalize.rs @@ -21,7 +21,7 @@ use solana_sdk_ids::system_program; use crate::fixtures::{ get_commit_record_account_data, get_delegation_metadata_data, get_delegation_record_data, COMMIT_NEW_STATE_ACCOUNT_DATA, - DELEGATED_PDA_ID, TEST_AUTHORITY, + DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY, }; mod fixtures; @@ -55,6 +55,8 @@ async fn test_finalize() { let ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); let tx = Transaction::new_signed_with_payer( &[ix], diff --git a/tests/test_lamports_settlement.rs b/tests/test_lamports_settlement.rs index 40cf41e7..cff8e14a 100644 --- a/tests/test_lamports_settlement.rs +++ b/tests/test_lamports_settlement.rs @@ -183,6 +183,8 @@ async fn test_commit_finalize_lamports_settlement() { let (commit_ix, _) = dlp_api::instruction_builder::commit_finalize( validator.pubkey(), delegated.pubkey(), + system_program::id(), + payer.pubkey(), &mut args, &[], ); @@ -233,6 +235,8 @@ async fn test_commit_finalize_lamports_settlement() { let (commit_ix, _) = dlp_api::instruction_builder::commit_finalize( validator.pubkey(), delegated.pubkey(), + system_program::id(), + payer.pubkey(), &mut args, &[], ); @@ -286,6 +290,8 @@ async fn test_commit_finalize_lamports_settlement() { let (commit_ix, _) = dlp_api::instruction_builder::commit_finalize( validator.pubkey(), delegated.pubkey(), + system_program::id(), + payer.pubkey(), &mut args, &[], ); @@ -339,6 +345,8 @@ async fn test_commit_finalize_lamports_settlement() { let (commit_ix, _) = dlp_api::instruction_builder::commit_finalize( validator.pubkey(), delegated.pubkey(), + system_program::id(), + payer.pubkey(), &mut args, &[], ); @@ -471,6 +479,7 @@ async fn test_commit_and_finalize_lamports_settlement() { fee_payer: &payer, label: "first finalize", delegated_account: delegated.pubkey(), + owner_program: system_program::id(), }) .await; @@ -516,6 +525,7 @@ async fn test_commit_and_finalize_lamports_settlement() { fee_payer: &payer, label: "second finalize", delegated_account: delegated.pubkey(), + owner_program: system_program::id(), }) .await; @@ -563,6 +573,7 @@ async fn test_commit_and_finalize_lamports_settlement() { fee_payer: &payer, label: "third finalize", delegated_account: delegated.pubkey(), + owner_program: system_program::id(), }) .await; @@ -610,6 +621,7 @@ async fn test_commit_and_finalize_lamports_settlement() { fee_payer: &payer, label: "fourth finalize", delegated_account: delegated.pubkey(), + owner_program: system_program::id(), }) .await; @@ -959,6 +971,7 @@ async fn finalize_and_maybe_undelegate( authority, blockhash, delegated_account, + owner_program, }) .await; if also_undelegate { @@ -1032,12 +1045,15 @@ struct FinalizeNewStateArgs<'a> { authority: &'a Keypair, blockhash: Hash, delegated_account: Pubkey, + owner_program: Pubkey, } async fn finalize_new_state(args: FinalizeNewStateArgs<'_>) { let ix = dlp_api::instruction_builder::finalize( args.authority.pubkey(), args.delegated_account, + args.owner_program, + args.authority.pubkey(), ); let tx = Transaction::new_signed_with_payer( &[ix], @@ -1257,12 +1273,15 @@ struct FinalizeWithFeePayerArgs<'a> { fee_payer: &'a Keypair, label: &'a str, delegated_account: Pubkey, + owner_program: Pubkey, } async fn finalize_with_fee_payer(args: FinalizeWithFeePayerArgs<'_>) { let ix = dlp_api::instruction_builder::finalize( args.authority.pubkey(), args.delegated_account, + args.owner_program, + args.authority.pubkey(), ); let tx = Transaction::new_signed_with_payer( &[ix], diff --git a/tests/test_request_undelegation.rs b/tests/test_request_undelegation.rs index 1db38f6c..1609ecb6 100644 --- a/tests/test_request_undelegation.rs +++ b/tests/test_request_undelegation.rs @@ -1,5 +1,6 @@ use dlp::solana_program; use dlp_api::{ + args::CommitFinalizeArgs, consts::DEFAULT_UNDELEGATION_REQUEST_TIMEOUT_SLOTS, pda::{ commit_record_pda_from_delegated_account, @@ -112,7 +113,6 @@ async fn test_request_undelegation_is_idempotent() { let SetupContext { banks, payer, - second_payer, blockhash, .. } = setup_env(SetupConfig { @@ -135,12 +135,11 @@ async fn test_request_undelegation_is_idempotent() { undelegation_request_pda_from_delegated_account(&DELEGATED_PDA_ID); let request_before = banks.get_account(request_pda).await.unwrap().unwrap(); - let second_ix = - request_undelegation_from_owner_program(second_payer.pubkey()); + let second_ix = request_undelegation_from_owner_program(payer.pubkey()); let second_tx = Transaction::new_signed_with_payer( &[second_ix], - Some(&second_payer.pubkey()), - &[&second_payer], + Some(&payer.pubkey()), + &[&payer], blockhash, ); let second_res = banks.process_transaction(second_tx).await; @@ -151,6 +150,56 @@ async fn test_request_undelegation_is_idempotent() { assert_eq!(request_after.data, request_before.data); } +#[tokio::test] +async fn test_request_undelegation_rejects_different_payer_on_existing_request() +{ + let SetupContext { + banks, + payer, + second_payer, + blockhash, + .. + } = setup_env(SetupConfig { + with_request_wrapper: true, + ..Default::default() + }) + .await; + + let first_ix = request_undelegation_from_owner_program(payer.pubkey()); + let first_tx = Transaction::new_signed_with_payer( + &[first_ix], + Some(&payer.pubkey()), + &[&payer], + blockhash, + ); + let first_res = banks.process_transaction(first_tx).await; + assert!(first_res.is_ok()); + + let second_ix = + request_undelegation_from_owner_program(second_payer.pubkey()); + let second_tx = Transaction::new_signed_with_payer( + &[second_ix], + Some(&second_payer.pubkey()), + &[&second_payer], + blockhash, + ); + let err = banks.process_transaction(second_tx).await.unwrap_err(); + assert!( + matches!( + err, + BanksClientError::TransactionError( + TransactionError::InstructionError( + 0, + InstructionError::Custom(code), + ) + ) if code + == dlp_api::error::DlpError::InvalidReimbursementAddressForDelegationRent + as u32 + ), + "expected InvalidReimbursementAddressForDelegationRent, got {err:?}" + ); +} + #[tokio::test] async fn test_request_undelegation_rejects_payload() { let SetupContext { @@ -276,8 +325,9 @@ async fn test_undelegate_with_request_closes_request() { blockhash, .. } = setup_env(SetupConfig { - metadata_undelegatable: true, + requester: UndelegationRequester::Validator, with_commit_accounts: true, + with_state_buffer: true, with_owner_program: true, with_fee_accounts: true, with_request_account: true, @@ -303,12 +353,14 @@ async fn test_undelegate_with_request_closes_request() { let ix_finalize = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), ); let ix_undelegate = dlp_api::instruction_builder::undelegate_with_request( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, - authority.pubkey(), + request_rent_payer.pubkey(), request_rent_payer.pubkey(), ); @@ -331,10 +383,214 @@ async fn test_undelegate_with_request_closes_request() { .unwrap() .unwrap() .lamports; - assert_eq!( - payer_lamports_after, - payer_lamports_before + request_lamports_before + assert!( + payer_lamports_after >= payer_lamports_before + request_lamports_before + ); +} + +#[tokio::test] +async fn test_finalize_auto_undelegates_owner_program_request_and_trailing_undelegate_noops( +) { + let SetupContext { + banks, + authority, + request_rent_payer, + blockhash, + .. + } = setup_env(SetupConfig { + requester: UndelegationRequester::OwnerProgram, + with_commit_accounts: true, + with_state_buffer: true, + with_owner_program: true, + with_fee_accounts: true, + with_request_account: true, + ..Default::default() + }) + .await; + + let request_pda = + undelegation_request_pda_from_delegated_account(&DELEGATED_PDA_ID); + let ix_finalize = dlp_api::instruction_builder::finalize( + authority.pubkey(), + DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), ); + let ix_undelegate = dlp_api::instruction_builder::undelegate_with_request( + authority.pubkey(), + DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), + request_rent_payer.pubkey(), + ); + + let tx = Transaction::new_signed_with_payer( + &[ix_finalize, ix_undelegate], + Some(&authority.pubkey()), + &[&authority], + blockhash, + ); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok(), "{res:?}"); + + assert!(banks.get_account(request_pda).await.unwrap().is_none()); + assert!(banks + .get_account(delegation_record_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + assert!(banks + .get_account(delegation_metadata_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + + let delegated_account = + banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); + assert_eq!(delegated_account.owner, DELEGATED_PDA_OWNER_ID); +} + +#[tokio::test] +async fn test_commit_finalize_auto_undelegates_owner_program_request() { + let SetupContext { + banks, + authority, + request_rent_payer, + blockhash, + .. + } = setup_env(SetupConfig { + requester: UndelegationRequester::OwnerProgram, + with_commit_accounts: true, + with_state_buffer: true, + with_owner_program: true, + with_fee_accounts: true, + with_request_account: true, + ..Default::default() + }) + .await; + + let request_pda = + undelegation_request_pda_from_delegated_account(&DELEGATED_PDA_ID); + let mut args = CommitFinalizeArgs { + commit_id: 1, + lamports: LAMPORTS_PER_SOL, + allow_undelegation: false.into(), + data_is_diff: false.into(), + bumps: Default::default(), + reserved_padding: Default::default(), + }; + let (ix, _) = dlp_api::instruction_builder::commit_finalize( + authority.pubkey(), + DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), + &mut args, + &COMMIT_NEW_STATE_ACCOUNT_DATA, + ); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&authority.pubkey()), + &[&authority], + blockhash, + ); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok(), "{res:?}"); + + assert!(banks.get_account(request_pda).await.unwrap().is_none()); + assert!(banks + .get_account(delegation_record_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + assert!(banks + .get_account(delegation_metadata_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + + let delegated_account = + banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); + assert_eq!(delegated_account.owner, DELEGATED_PDA_OWNER_ID); +} + +#[tokio::test] +async fn test_commit_finalize_from_buffer_auto_undelegates_owner_program_request( +) { + let SetupContext { + banks, + authority, + request_rent_payer, + blockhash, + .. + } = setup_env(SetupConfig { + requester: UndelegationRequester::OwnerProgram, + with_commit_accounts: true, + with_state_buffer: true, + with_owner_program: true, + with_fee_accounts: true, + with_request_account: true, + ..Default::default() + }) + .await; + + let request_pda = + undelegation_request_pda_from_delegated_account(&DELEGATED_PDA_ID); + let state_buffer_pda = + Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; + let mut args = CommitFinalizeArgs { + commit_id: 1, + lamports: LAMPORTS_PER_SOL, + allow_undelegation: false.into(), + data_is_diff: false.into(), + bumps: Default::default(), + reserved_padding: Default::default(), + }; + let (ix, _) = dlp_api::instruction_builder::commit_finalize_from_buffer( + authority.pubkey(), + DELEGATED_PDA_ID, + state_buffer_pda, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), + &mut args, + ); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&authority.pubkey()), + &[&authority], + blockhash, + ); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok(), "{res:?}"); + + assert!(banks.get_account(request_pda).await.unwrap().is_none()); + assert!(banks + .get_account(delegation_record_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + assert!(banks + .get_account(delegation_metadata_pda_from_delegated_account( + &DELEGATED_PDA_ID, + )) + .await + .unwrap() + .is_none()); + + let delegated_account = + banks.get_account(DELEGATED_PDA_ID).await.unwrap().unwrap(); + assert_eq!(delegated_account.owner, DELEGATED_PDA_OWNER_ID); } #[tokio::test] @@ -346,8 +602,9 @@ async fn test_undelegate_with_malformed_optional_request_accounts_rejected() { blockhash, .. } = setup_env(SetupConfig { - metadata_undelegatable: true, + requester: UndelegationRequester::Validator, with_commit_accounts: true, + with_state_buffer: true, with_owner_program: true, with_fee_accounts: true, with_request_account: true, @@ -358,13 +615,15 @@ async fn test_undelegate_with_malformed_optional_request_accounts_rejected() { let ix_finalize = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + request_rent_payer.pubkey(), ); let mut ix_undelegate = dlp_api::instruction_builder::undelegate_with_request( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, - authority.pubkey(), + request_rent_payer.pubkey(), request_rent_payer.pubkey(), ); ix_undelegate.accounts.pop(); @@ -479,17 +738,32 @@ impl Default for DelegatedAccountSetup { } } -#[derive(Default)] struct SetupConfig { delegated_account: DelegatedAccountSetup, with_request_wrapper: bool, - metadata_undelegatable: bool, + requester: UndelegationRequester, with_commit_accounts: bool, + with_state_buffer: bool, with_owner_program: bool, with_fee_accounts: bool, with_request_account: bool, } +impl Default for SetupConfig { + fn default() -> Self { + Self { + delegated_account: DelegatedAccountSetup::OffCurvePda, + with_request_wrapper: false, + requester: UndelegationRequester::None, + with_commit_accounts: false, + with_state_buffer: false, + with_owner_program: false, + with_fee_accounts: false, + with_request_account: false, + } + } +} + struct SetupContext { banks: BanksClient, payer: Keypair, @@ -540,28 +814,26 @@ async fn setup_env(config: SetupConfig) -> SetupContext { ); add_delegated_account(&mut program_test, delegated_account); - match config.delegated_account { - DelegatedAccountSetup::OffCurvePda => { - add_delegation_accounts_with_metadata( - &mut program_test, - delegated_account, - authority.pubkey(), - authority.pubkey(), - config.metadata_undelegatable, - false, - ); - } - DelegatedAccountSetup::OnCurveKeypair => { - add_delegation_accounts_with_metadata( - &mut program_test, - delegated_account, - delegated_on_curve.pubkey(), - payer.pubkey(), - config.metadata_undelegatable, - true, - ); - } - } + let metadata_rent_payer = if config.with_request_account { + request_rent_payer.pubkey() + } else { + payer.pubkey() + }; + let delegation_authority = match config.delegated_account { + DelegatedAccountSetup::OffCurvePda => authority.pubkey(), + DelegatedAccountSetup::OnCurveKeypair => delegated_on_curve.pubkey(), + }; + add_delegation_accounts_with_metadata_requester( + &mut program_test, + delegated_account, + delegation_authority, + metadata_rent_payer, + config.requester, + matches!( + config.delegated_account, + DelegatedAccountSetup::OnCurveKeypair + ), + ); if config.with_commit_accounts { add_commit_accounts( @@ -570,6 +842,9 @@ async fn setup_env(config: SetupConfig) -> SetupContext { authority.pubkey(), ); } + if config.with_state_buffer { + add_state_buffer_account(&mut program_test, authority.pubkey()); + } if config.with_owner_program { add_owner_program(&mut program_test); } @@ -627,18 +902,18 @@ fn add_delegated_account(program_test: &mut ProgramTest, pubkey: Pubkey) { ); } -fn add_delegation_accounts_with_metadata( +fn add_delegation_accounts_with_metadata_requester( program_test: &mut ProgramTest, delegated_account: Pubkey, authority: Pubkey, rent_payer: Pubkey, - undelegatable: bool, + requester: UndelegationRequester, on_curve: bool, ) { let delegation_record_data = if on_curve { get_delegation_record_on_curve_data(authority, Some(LAMPORTS_PER_SOL)) } else { - get_delegation_record_data(authority, None) + get_delegation_record_data(authority, Some(LAMPORTS_PER_SOL)) }; program_test.add_account( delegation_record_pda_from_delegated_account(&delegated_account), @@ -653,9 +928,11 @@ fn add_delegation_accounts_with_metadata( ); let delegation_metadata_data = if on_curve { - get_delegation_metadata_data_on_curve(rent_payer, Some(undelegatable)) + get_delegation_metadata_data_on_curve_with_requester( + rent_payer, requester, + ) } else { - get_delegation_metadata_data(rent_payer, Some(undelegatable)) + get_delegation_metadata_data_with_requester(rent_payer, requester) }; program_test.add_account( delegation_metadata_pda_from_delegated_account(&delegated_account), @@ -670,6 +947,34 @@ fn add_delegation_accounts_with_metadata( ); } +fn get_delegation_metadata_data_on_curve_with_requester( + rent_payer: Pubkey, + requester: UndelegationRequester, +) -> Vec { + let data = get_delegation_metadata_data_on_curve(rent_payer, Some(false)); + set_delegation_metadata_requester(data, requester) +} + +fn get_delegation_metadata_data_with_requester( + rent_payer: Pubkey, + requester: UndelegationRequester, +) -> Vec { + let data = get_delegation_metadata_data(rent_payer, Some(false)); + set_delegation_metadata_requester(data, requester) +} + +fn set_delegation_metadata_requester( + data: Vec, + requester: UndelegationRequester, +) -> Vec { + let mut metadata = + DelegationMetadata::try_from_bytes_with_discriminator(&data).unwrap(); + metadata.undelegation_requester = requester; + let mut bytes = vec![]; + metadata.to_bytes_with_discriminator(&mut bytes).unwrap(); + bytes +} + fn add_commit_accounts( program_test: &mut ProgramTest, delegated_account: Pubkey, @@ -699,6 +1004,21 @@ fn add_commit_accounts( ); } +fn add_state_buffer_account(program_test: &mut ProgramTest, authority: Pubkey) { + let state_buffer_pda = + Pubkey::find_program_address(&[b"state_buffer"], &authority).0; + program_test.add_account( + state_buffer_pda, + Account { + lamports: LAMPORTS_PER_SOL, + data: COMMIT_NEW_STATE_ACCOUNT_DATA.into(), + owner: dlp_api::id(), + executable: false, + rent_epoch: 0, + }, + ); +} + fn add_owner_program(program_test: &mut ProgramTest) { let data = read_file("tests/buffers/test_delegation.so"); program_test.add_account( diff --git a/tests/test_undelegate.rs b/tests/test_undelegate.rs index 0e9f74ed..e05b08a1 100644 --- a/tests/test_undelegate.rs +++ b/tests/test_undelegate.rs @@ -43,6 +43,8 @@ async fn test_finalize_and_undelegate() { let ix_finalize = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, + DELEGATED_PDA_OWNER_ID, + authority.pubkey(), ); // Create the undelegate tx diff --git a/tests/test_undelegate_with_rollback_after_timeout.rs b/tests/test_undelegate_with_rollback_after_timeout.rs index de284b10..5dcf0d9b 100644 --- a/tests/test_undelegate_with_rollback_after_timeout.rs +++ b/tests/test_undelegate_with_rollback_after_timeout.rs @@ -46,6 +46,12 @@ mod fixtures; const TEST_PDA_SEED: &[u8] = b"test-pda"; +#[derive(Clone, Copy)] +enum RentPayerMode { + Split, + Shared, +} + #[tokio::test] async fn test_undelegate_with_rollback_after_timeout_rejects_wrong_request_payer( ) { @@ -358,7 +364,14 @@ async fn test_re_request_is_idempotent_without_extending_timeout() { _delegation_rent_payer, _, blockhash, - ) = setup_request_timeout_env_with_commit_ids(false, 0, 1, 0).await; + ) = setup_request_timeout_env_with_commit_ids( + false, + 0, + 1, + 0, + RentPayerMode::Shared, + ) + .await; let request_pda = undelegation_request_pda_from_delegated_account(&DELEGATED_PDA_ID); @@ -408,6 +421,7 @@ async fn setup_request_timeout_env( expires_at_slot, 0, 0, + RentPayerMode::Split, ) .await } @@ -417,6 +431,7 @@ async fn setup_request_timeout_env_with_commit_ids( expires_at_slot: u64, delegation_last_commit_id: u64, request_last_commit_id: u64, + rent_payer_mode: RentPayerMode, ) -> (BanksClient, Keypair, Keypair, Keypair, Keypair, Hash) { let mut program_test = ProgramTest::default(); program_test.prefer_bpf(true); @@ -439,9 +454,13 @@ async fn setup_request_timeout_env_with_commit_ids( add_system_account(&mut program_test, delegation_rent_payer.pubkey()); add_system_account(&mut program_test, validator.pubkey()); add_delegated_account(&mut program_test); + let delegation_rent_payer_pubkey = match rent_payer_mode { + RentPayerMode::Split => delegation_rent_payer.pubkey(), + RentPayerMode::Shared => request_rent_payer.pubkey(), + }; add_delegation_accounts( &mut program_test, - delegation_rent_payer.pubkey(), + delegation_rent_payer_pubkey, delegation_last_commit_id, ); add_request_account(