Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ pub enum DlpError {
InvalidDiscriminator = 39,
#[error("Invalid delegation record deserialization")]
InvalidDelegationRecordData = 40,
#[error("Delegated account is already closed")]
DelegateAccountClosed = 41,
#[error("An infallible error is encountered possibly due to logic error")]
InfallibleError = 100,
}
Expand Down
7 changes: 7 additions & 0 deletions src/processor/fast/undelegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ use super::{
///
/// - Close the delegation metadata
/// - Close the delegation record
/// - If delegated account is closed then stop the process
/// - If delegated account has no data, assign to prev owner (and stop here)
/// - If there's data, create an "undelegate_buffer" and store the data in it
/// - Close the original delegated account
Expand All @@ -87,6 +88,12 @@ pub fn process_undelegate(
return Err(ProgramError::NotEnoughAccountKeys);
};

// Reject undelegation of closed account
if delegated_account.lamports() == 0 && delegated_account.data_is_empty() {
log!("Delegated account is already closed");
return Err(DlpError::DelegateAccountClosed.into());
}

// Check accounts
require_signer(validator, "validator")?;
require_owned_pda(delegated_account, &crate::fast::ID, "delegated account")?;
Expand Down
137 changes: 137 additions & 0 deletions tests/test_undelegate_close_account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use dlp::pda::{
delegation_metadata_pda_from_delegated_account, delegation_record_pda_from_delegated_account,
fees_vault_pda, validator_fees_vault_pda_from_validator,
};
use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, rent::Rent, system_program};
use solana_program_test::{read_file, BanksClient, ProgramTest};
use solana_sdk::{
account::Account,
signature::{Keypair, Signer},
transaction::Transaction,
};

use crate::fixtures::{
get_delegation_metadata_data, get_delegation_record_data, DELEGATED_PDA_ID,
DELEGATED_PDA_OWNER_ID, TEST_AUTHORITY,
};

mod fixtures;

#[tokio::test]
async fn test_undelegate_close_account() {
// Setup
let (banks, _, authority, blockhash) = setup_program_test_env().await;

// Create the undelegate tx
let ix_undelegate = dlp::instruction_builder::undelegate(
authority.pubkey(),
DELEGATED_PDA_ID,
DELEGATED_PDA_OWNER_ID,
authority.pubkey(),
);

// Submit the transaction
let tx = Transaction::new_signed_with_payer(
&[ix_undelegate],
Some(&authority.pubkey()),
&[&authority],
blockhash,
);
let res = banks.process_transaction(tx).await;
assert!(res.is_err(), "Delegate account is already closed");
}

async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) {
let mut program_test = ProgramTest::new("dlp", dlp::ID, None);
program_test.prefer_bpf(true);
let authority = Keypair::from_bytes(&TEST_AUTHORITY).unwrap();

program_test.add_account(
authority.pubkey(),
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![],
owner: system_program::id(),
executable: false,
rent_epoch: 0,
},
);

// Setup a Closed delegated PDA
program_test.add_account(
DELEGATED_PDA_ID,
Account {
lamports: 0,
data: vec![],
owner: dlp::id(),
executable: false,
rent_epoch: 0,
},
);

// Setup the delegated record PDA
let delegation_record_data = get_delegation_record_data(authority.pubkey(), None);
program_test.add_account(
delegation_record_pda_from_delegated_account(&DELEGATED_PDA_ID),
Account {
lamports: Rent::default().minimum_balance(delegation_record_data.len()),
data: delegation_record_data,
owner: dlp::id(),
executable: false,
rent_epoch: 0,
},
);

// Setup the delegated metadata PDA
let delegation_metadata_data = get_delegation_metadata_data(authority.pubkey(), Some(true));
program_test.add_account(
delegation_metadata_pda_from_delegated_account(&DELEGATED_PDA_ID),
Account {
lamports: Rent::default().minimum_balance(delegation_metadata_data.len()),
data: delegation_metadata_data,
owner: dlp::id(),
executable: false,
rent_epoch: 0,
},
);

// Setup program to test undelegation
let data = read_file("tests/buffers/test_delegation.so");
program_test.add_account(
DELEGATED_PDA_OWNER_ID,
Account {
lamports: Rent::default().minimum_balance(data.len()),
data,
owner: solana_sdk::bpf_loader::id(),
executable: true,
rent_epoch: 0,
},
);

// Setup the protocol fees vault
program_test.add_account(
fees_vault_pda(),
Account {
lamports: Rent::default().minimum_balance(0),
data: vec![],
owner: dlp::id(),
executable: false,
rent_epoch: 0,
},
);

// Setup the validator fees vault
program_test.add_account(
validator_fees_vault_pda_from_validator(&authority.pubkey()),
Account {
lamports: LAMPORTS_PER_SOL,
data: vec![],
owner: dlp::id(),
executable: false,
rent_epoch: 0,
},
);

let (banks, payer, blockhash) = program_test.start().await;
(banks, payer, authority, blockhash)
}