Skip to content

Commit cc8f6a8

Browse files
committed
fix: fixed undelegation of ephemeral balance. Now undelegated to system program. Added new version of close_ephemeral_balance to support it. kept old one for backward-compatability
1 parent 4af7f1c commit cc8f6a8

10 files changed

+274
-41
lines changed

src/discriminator.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ pub enum DlpDiscriminator {
3333
CommitStateFromBuffer = 13,
3434
/// See [crate::processor::process_close_validator_fees_vault] for docs.
3535
CloseValidatorFeesVault = 14,
36+
/// See [crate::processor::process_undelegate_ephemeral_balance] for docs.
37+
UndelegateEphemeralBalance = 15,
38+
/// See [crate::processor::process_close_ephemeral_balance_v1] for docs.
39+
CloseEphemeralBalanceV1 = 16
3640
}
3741

3842
impl DlpDiscriminator {
@@ -45,7 +49,8 @@ impl DlpDiscriminator {
4549
impl TryFrom<[u8; 8]> for DlpDiscriminator {
4650
type Error = ProgramError;
4751
fn try_from(bytes: [u8; 8]) -> Result<Self, Self::Error> {
48-
match bytes[0] {
52+
let discriminator = u64::from_le_bytes(bytes);
53+
match discriminator {
4954
0x0 => Ok(DlpDiscriminator::Delegate),
5055
0x1 => Ok(DlpDiscriminator::CommitState),
5156
0x2 => Ok(DlpDiscriminator::Finalize),
@@ -60,6 +65,8 @@ impl TryFrom<[u8; 8]> for DlpDiscriminator {
6065
0xc => Ok(DlpDiscriminator::ProtocolClaimFees),
6166
0xd => Ok(DlpDiscriminator::CommitStateFromBuffer),
6267
0xe => Ok(DlpDiscriminator::CloseValidatorFeesVault),
68+
0xf => Ok(DlpDiscriminator::UndelegateEphemeralBalance),
69+
0x10 => Ok(DlpDiscriminator::CloseEphemeralBalanceV1),
6370
_ => Err(ProgramError::InvalidInstructionData),
6471
}
6572
}

src/instruction_builder/close_ephemeral_balance.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
use solana_program::instruction::Instruction;
2-
use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
2+
use solana_program::{instruction::AccountMeta, pubkey::Pubkey, system_program};
33

44
use crate::discriminator::DlpDiscriminator;
55
use crate::pda::ephemeral_balance_pda_from_payer;
66

77
/// Creates instruction to close an ephemeral balance account
8-
/// See [crate::processor::process_close_ephemeral_balance] for docs.
8+
/// See [crate::processor::process_close_ephemeral_balance_v1] for docs.
9+
/// [crate::processor::process_close_ephemeral_balance] now deprecated
910
pub fn close_ephemeral_balance(payer: Pubkey, index: u8) -> Instruction {
1011
let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&payer, index);
1112
Instruction {
1213
program_id: crate::id(),
1314
accounts: vec![
1415
AccountMeta::new(payer, true),
1516
AccountMeta::new(ephemeral_balance_pda, false),
17+
AccountMeta::new_readonly(system_program::id(), false),
1618
],
1719
data: [
18-
DlpDiscriminator::CloseEphemeralBalance.to_vec(),
20+
DlpDiscriminator::CloseEphemeralBalanceV1.to_vec(),
1921
vec![index],
2022
]
2123
.concat(),

src/instruction_builder/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod init_validator_fees_vault;
1111
mod protocol_claim_fees;
1212
mod top_up_ephemeral_balance;
1313
mod undelegate;
14+
mod undelegate_ephemeral_balance;
1415
mod validator_claim_fees;
1516
mod whitelist_validator_for_program;
1617

@@ -26,5 +27,6 @@ pub use init_validator_fees_vault::*;
2627
pub use protocol_claim_fees::*;
2728
pub use top_up_ephemeral_balance::*;
2829
pub use undelegate::*;
30+
pub use undelegate_ephemeral_balance::*;
2931
pub use validator_claim_fees::*;
3032
pub use whitelist_validator_for_program::*;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use crate::discriminator::DlpDiscriminator;
2+
use crate::instruction_builder::undelegate;
3+
use solana_program::instruction::Instruction;
4+
use solana_program::pubkey::Pubkey;
5+
6+
/// Builds an undelegate instruction.
7+
/// See [crate::processor::process_undelegate] for docs.
8+
#[allow(clippy::too_many_arguments)]
9+
pub fn undelegate_ephemeral_balance(
10+
validator: Pubkey,
11+
delegated_account: Pubkey,
12+
rent_reimbursement: Pubkey,
13+
) -> Instruction {
14+
let mut ix = undelegate(validator, delegated_account, crate::ID, rent_reimbursement);
15+
ix.data = DlpDiscriminator::UndelegateEphemeralBalance.to_vec();
16+
17+
ix
18+
}

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ pub fn process_instruction(
9393
discriminator::DlpDiscriminator::CloseValidatorFeesVault => {
9494
processor::process_close_validator_fees_vault(program_id, accounts, data)?
9595
}
96+
discriminator::DlpDiscriminator::UndelegateEphemeralBalance => {
97+
processor::process_undelegate_ephemeral_balance(program_id, accounts, data)?
98+
}
99+
discriminator::DlpDiscriminator::CloseEphemeralBalanceV1 => {
100+
processor::process_close_ephemeral_balance_v1(program_id, accounts, data)?
101+
}
96102
}
97103
Ok(())
98104
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::ephemeral_balance_seeds_from_payer;
2+
use crate::processor::utils::loaders::{load_pda, load_signer};
3+
use solana_program::msg;
4+
use solana_program::program::invoke_signed;
5+
use solana_program::program_error::ProgramError;
6+
use solana_program::system_instruction::transfer;
7+
use solana_program::{
8+
account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program,
9+
};
10+
11+
/// Process the closing of an ephemeral balance account
12+
///
13+
/// Accounts:
14+
///
15+
/// 0: `[signer]` payer to pay for the transaction and receive the refund
16+
/// 1: `[writable]` ephemeral balance account we are closing
17+
/// 2: `[]` the system program
18+
///
19+
/// Requirements:
20+
///
21+
/// - ephemeral balance account is initialized
22+
///
23+
/// Steps:
24+
///
25+
/// 1. Closes the ephemeral balance account and refunds the payer with the
26+
/// escrowed lamports
27+
pub fn process_close_ephemeral_balance_v1(
28+
_program_id: &Pubkey,
29+
accounts: &[AccountInfo],
30+
data: &[u8],
31+
) -> ProgramResult {
32+
let index = *data.first().ok_or(ProgramError::InvalidInstructionData)?;
33+
34+
// Load Accounts
35+
let [payer, ephemeral_balance_account, system_program] = accounts else {
36+
return Err(ProgramError::NotEnoughAccountKeys);
37+
};
38+
39+
load_signer(payer, "payer")?;
40+
41+
let ephemeral_balance_seeds: &[&[u8]] = ephemeral_balance_seeds_from_payer!(payer.key, index);
42+
let ephemeral_balance_bump = load_pda(
43+
ephemeral_balance_account,
44+
ephemeral_balance_seeds,
45+
&crate::id(),
46+
true,
47+
"ephemeral balance",
48+
)?;
49+
if ephemeral_balance_account.owner != &system_program::id() {
50+
msg!(
51+
"ephemeral balance expected to be owned by system program: {}",
52+
system_program::id()
53+
);
54+
return Err(ProgramError::InvalidAccountOwner);
55+
}
56+
57+
let amount = ephemeral_balance_account.lamports();
58+
let ephemeral_balance_bump_slice: &[u8] = &[ephemeral_balance_bump];
59+
let ephemeral_balance_signer_seeds =
60+
[ephemeral_balance_seeds, &[ephemeral_balance_bump_slice]].concat();
61+
invoke_signed(
62+
&transfer(ephemeral_balance_account.key, payer.key, amount),
63+
&[
64+
ephemeral_balance_account.clone(),
65+
payer.clone(),
66+
system_program.clone(),
67+
],
68+
&[&ephemeral_balance_signer_seeds],
69+
)?;
70+
71+
Ok(())
72+
}

src/processor/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod close_ephemeral_balance;
2+
mod close_ephemeral_balance_v1;
23
mod close_validator_fees_vault;
34
mod commit_state;
45
mod commit_state_from_buffer;
@@ -10,11 +11,13 @@ mod init_validator_fees_vault;
1011
mod protocol_claim_fees;
1112
mod top_up_ephemeral_balance;
1213
mod undelegate;
14+
mod undelegate_ephemeral_balance;
1315
mod utils;
1416
mod validator_claim_fees;
1517
mod whitelist_validator_for_program;
1618

1719
pub use close_ephemeral_balance::*;
20+
pub use close_ephemeral_balance_v1::*;
1821
pub use close_validator_fees_vault::*;
1922
pub use commit_state::*;
2023
pub use commit_state_from_buffer::*;
@@ -26,5 +29,6 @@ pub use init_validator_fees_vault::*;
2629
pub use protocol_claim_fees::*;
2730
pub use top_up_ephemeral_balance::*;
2831
pub use undelegate::*;
32+
pub use undelegate_ephemeral_balance::*;
2933
pub use validator_claim_fees::*;
3034
pub use whitelist_validator_for_program::*;

src/processor/top_up_ephemeral_balance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn process_top_up_ephemeral_balance(
5656
create_pda(
5757
ephemeral_balance_account,
5858
&system_program::id(),
59-
8,
59+
0,
6060
ephemeral_balance_seeds_from_payer!(pubkey.key, args.index),
6161
bump_ephemeral_balance,
6262
system_program,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use crate::instruction_builder::undelegate;
2+
use solana_program::msg;
3+
use solana_program::program::invoke;
4+
use solana_program::program_error::ProgramError;
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program,
7+
};
8+
9+
/// Undelegate ephemeral balance
10+
///
11+
/// Accounts:
12+
///
13+
/// 0: `[signer]` the validator account
14+
/// 1: `[writable]` the delegated account
15+
/// 2: `[]` the owner program of the delegated account
16+
/// 3: `[writable]` the undelegate buffer PDA we use to store the data temporarily
17+
/// 4: `[]` the commit state PDA
18+
/// 5: `[]` the commit record PDA
19+
/// 6: `[writable]` the delegation record PDA
20+
/// 7: `[writable]` the delegation metadata PDA
21+
/// 8: `[]` the rent reimbursement account
22+
/// 9: `[writable]` the protocol fees vault account
23+
/// 10: `[writable]` the validator fees vault account
24+
/// 11: `[]` the system program
25+
///
26+
/// Requirements:
27+
///
28+
/// - delegated account is owned by delegation program
29+
/// - delegation record is initialized
30+
/// - delegation metadata is initialized
31+
/// - protocol fees vault is initialized
32+
/// - validator fees vault is initialized
33+
/// - commit state is uninitialized
34+
/// - commit record is uninitialized
35+
/// - delegated account is NOT undelegatable
36+
/// - owner program account matches the owner in the delegation record
37+
/// - rent reimbursement account matches the rent payer in the delegation metadata
38+
///
39+
/// Steps:
40+
///
41+
/// - Undelegate using CPI into [`crate::processor::undelegate`]
42+
/// - Assigns ownership back to system program
43+
pub fn process_undelegate_ephemeral_balance(
44+
_program_id: &Pubkey,
45+
accounts: &[AccountInfo],
46+
_data: &[u8],
47+
) -> ProgramResult {
48+
let [validator, delegated_account, owner_program, _, _, _, _, _, rent_reimbursement, _, _, _] =
49+
accounts
50+
else {
51+
return Err(ProgramError::NotEnoughAccountKeys);
52+
};
53+
54+
if owner_program.key != &crate::ID {
55+
msg!(
56+
"Unexpected owner program. expected dlp, got: {}",
57+
owner_program.key
58+
);
59+
return Err(ProgramError::IncorrectProgramId);
60+
}
61+
62+
// Propagate to undelegate which also runs all necessary checks.
63+
let undelegate_ix = undelegate(
64+
*validator.key,
65+
*delegated_account.key,
66+
*owner_program.key,
67+
*rent_reimbursement.key,
68+
);
69+
invoke(&undelegate_ix, accounts)?;
70+
71+
// Assign ownership back to system_program
72+
delegated_account.assign(&system_program::ID);
73+
Ok(())
74+
}

0 commit comments

Comments
 (0)