Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bf93ac1
feat: Impl RequestUndelegation
snawaz Jun 19, 2026
4d45bec
add CarryOverRequestedUndelegation
snawaz Jun 22, 2026
fc36480
address rabbit's feedback
snawaz Jun 24, 2026
723ead0
fix lint issues
snawaz Jun 24, 2026
4ac27be
address rabbit's feedback
snawaz Jun 24, 2026
3cd51cc
Use 30min as timeout for validator
snawaz Jun 24, 2026
2f367f4
add `df -h` to CI
snawaz Jun 24, 2026
7cdfee4
rename CarryOverRequestedUndelegation -> UndelegateAfterRequestTimeout
snawaz Jun 24, 2026
4c1531e
Use requires macros where possible
snawaz Jun 24, 2026
f87a7e5
replace parse_timeout_slots with RequestUndelegationArgs
snawaz Jun 24, 2026
607a737
rename UndelegateAfterRequestTimeout -> UndelegateWithRollbackAfterTi…
snawaz Jun 26, 2026
28bc3c1
revert unrelated changes regaring name changes
snawaz Jun 26, 2026
9449732
Remove request undelegation timeout args; timeout not configurable now
snawaz Jun 27, 2026
23fdbe8
remove wheels
snawaz Jun 27, 2026
7f6c793
Require owner-program authorization for rollback
snawaz Jun 27, 2026
fb47f7c
remove is_writable checks
snawaz Jun 27, 2026
ba823bb
rename is_undelegatable to undelegatable and replace bool with enum U…
snawaz Jun 27, 2026
0cca5a6
rename last_update_nonce -> last_commit_id
snawaz Jun 27, 2026
3f0768b
make request_undelegation truly idempotent
snawaz Jun 27, 2026
9301d09
rename undelegatable -> undelegation_requester
snawaz Jun 27, 2026
fb847a7
be compatible with borsh 0.10
snawaz Jun 28, 2026
ac0600f
increase timeout slots and remove stale test
snawaz Jun 28, 2026
ac3af3b
Allow UndelegationRequester::OwnerProgram to go through and fail iff …
snawaz Jun 28, 2026
db7ad48
remove else-block in process_request_undelegation()
snawaz Jul 1, 2026
6284135
use config-driven undelegation request setup in tests
snawaz Jul 2, 2026
212d344
require undelegation request accounts for owner-program requests in U…
snawaz Jul 6, 2026
074a1a4
ensure rent_payer is same as the delegation rent payer
snawaz Jul 6, 2026
2ab3a15
cleanup
snawaz Jul 7, 2026
26d01c4
Shrink undelegation request state, removing fields present in metadat…
snawaz Jul 7, 2026
a5cb190
Require allow_undelegation = true for owner-requested commits
snawaz Jul 8, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:
solana --version
solana-keygen new --silent --no-bip39-passphrase

- name: log disk space
Comment thread
GabrielePicco marked this conversation as resolved.
run: df -h

- name: run build
run: |
cargo build
Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dlp-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ thiserror = { version = ">=1" }
serde = { version = "1.0.228", default-features = false, features = ["derive"] }

solana-pubkey-compat = { package = "solana-pubkey", version = "2.4", features = ["borsh", "bytemuck", "curve25519"] }
wheels = { git = "https://github.com/magicblock-labs/magicblock-wheels.git", rev = "5b83b56" }
Comment thread
GabrielePicco marked this conversation as resolved.
Outdated

[dev-dependencies]
rand = { version = "0.8.5", features = ["small_rng"] }
Expand Down
2 changes: 2 additions & 0 deletions dlp-api/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod commit_state;
mod delegate;
mod delegate_ephemeral_balance;
mod delegate_with_actions;
mod request_undelegation;
mod top_up_ephemeral_balance;
mod types;
mod validator_claim_fees;
Expand All @@ -13,6 +14,7 @@ pub use commit_state::*;
pub use delegate::*;
pub use delegate_ephemeral_balance::*;
pub use delegate_with_actions::*;
pub use request_undelegation::*;
pub use top_up_ephemeral_balance::*;
pub use types::*;
pub use validator_claim_fees::*;
Expand Down
6 changes: 6 additions & 0 deletions dlp-api/src/args/request_undelegation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use wheels::variable_offset_layout;

#[variable_offset_layout(buffer_offset = 0)]
pub struct RequestUndelegationArgs {
pub timeout_slots: Option<u16>, // number of slots as timeout
Comment thread
GabrielePicco marked this conversation as resolved.
Outdated
}
4 changes: 4 additions & 0 deletions dlp-api/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pub const COMMIT_FEE_LAMPORTS: u64 = 100_000;
/// Fixed fee per delegation session (0.0003 SOL).
pub const SESSION_FEE_LAMPORTS: u64 = 300_000;

/// Default and minimum timeout for requested undelegation.
/// Assuming 1 slot is roughly 400ms, then 4500 slots = 30min.
pub const DEFAULT_UNDELEGATION_REQUEST_TIMEOUT_SLOTS: u64 = 4500;

/// The discriminator for the external undelegate instruction.
pub const EXTERNAL_UNDELEGATE_DISCRIMINATOR: [u8; 8] =
[196, 28, 41, 206, 48, 37, 51, 167];
Expand Down
6 changes: 6 additions & 0 deletions dlp-api/src/discriminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ pub enum DlpDiscriminator {

/// See [crate::processor::process_delegate_magic_fee_vault] for docs.
DelegateMagicFeeVault = 25,

/// See [crate::processor::process_request_undelegation] for docs.
RequestUndelegation = 26,

/// See [crate::processor::process_undelegate_with_rollback_after_timeout] for docs.
UndelegateWithRollbackAfterTimeout = 27,
}

impl DlpDiscriminator {
Expand Down
30 changes: 30 additions & 0 deletions dlp-api/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,36 @@ pub enum DlpError {
)]
InsufficientRent = 43,

#[error("Undelegation request PDA invalid seeds")]
UndelegationRequestInvalidSeeds = 44,

#[error("Undelegation request PDA invalid account owner")]
UndelegationRequestInvalidAccountOwner = 45,

#[error("Undelegation request PDA is already initialized")]
UndelegationRequestAlreadyInitialized = 46,

#[error("Undelegation request PDA immutable")]
UndelegationRequestImmutable = 47,

#[error("Invalid undelegation request")]
InvalidUndelegationRequest = 48,

#[error("Request undelegation is only supported for off-curve delegated accounts")]
RequestUndelegationOnCurveAccount = 49,

#[error("Undelegation request timeout is below the minimum")]
UndelegationRequestTimeoutTooShort = 50,

#[error("Undelegation request has not expired")]
UndelegationRequestNotExpired = 51,

#[error("Invalid pending commit state for request-timeout undelegation")]
InvalidPendingCommitState = 52,

#[error("Commit nonce did not match. Re-request to update commit nonce")]
RollbackCommitNonceMismatch = 53,

#[error("An infallible error is encountered possibly due to logic error")]
InfallibleError = 100,
}
Expand Down
4 changes: 4 additions & 0 deletions dlp-api/src/instruction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ mod init_magic_fee_vault;
mod init_protocol_fees_vault;
mod init_validator_fees_vault;
mod protocol_claim_fees;
mod request_undelegation;
mod top_up_ephemeral_balance;
mod types;
mod undelegate;
mod undelegate_confined_account;
mod undelegate_with_rollback_after_timeout;
mod validator_claim_fees;
mod whitelist_validator_for_program;

Expand All @@ -45,9 +47,11 @@ pub use init_magic_fee_vault::*;
pub use init_protocol_fees_vault::*;
pub use init_validator_fees_vault::*;
pub use protocol_claim_fees::*;
pub use request_undelegation::*;
pub use top_up_ephemeral_balance::*;
pub use types::*;
pub use undelegate::*;
pub use undelegate_confined_account::*;
pub use undelegate_with_rollback_after_timeout::*;
pub use validator_claim_fees::*;
pub use whitelist_validator_for_program::*;
81 changes: 81 additions & 0 deletions dlp-api/src/instruction_builder/request_undelegation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use dlp::{
discriminator::DlpDiscriminator,
pda::{
delegation_metadata_pda_from_delegated_account,
delegation_record_pda_from_delegated_account,
undelegation_request_pda_from_delegated_account,
},
total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS,
};
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use solana_sdk_ids::system_program;
use wheels::layout::Encodable;

use crate::{
args::RequestUndelegationArgs,
compat::{Compatize, Modernize},
};

/// Builds a request undelegation instruction.
/// See [dlp::processor::process_request_undelegation] for docs.
pub fn request_undelegation(
payer: Pubkey,
delegated_account: Pubkey,
owner_program: Pubkey,
args: RequestUndelegationArgs,
) -> Instruction {
let delegated_account_compat = delegated_account.compatize();
let undelegation_request_pda =
undelegation_request_pda_from_delegated_account(
&delegated_account_compat,
)
.modernize();
let delegation_record_pda =
delegation_record_pda_from_delegated_account(&delegated_account_compat)
.modernize();
let delegation_metadata_pda =
delegation_metadata_pda_from_delegated_account(
&delegated_account_compat,
)
.modernize();
Instruction {
program_id: dlp::id().modernize(),
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new_readonly(delegated_account, true),
AccountMeta::new_readonly(owner_program, false),
AccountMeta::new(undelegation_request_pda, false),
AccountMeta::new_readonly(delegation_record_pda, false),
AccountMeta::new_readonly(delegation_metadata_pda, false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: {
let mut data = DlpDiscriminator::RequestUndelegation.to_vec();
data.extend_from_slice(&args.encode().unwrap());
data
},
}
}

///
/// Returns accounts-data-size budget for request undelegation instruction.
///
/// This value can be used with ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit
///
pub fn request_undelegation_size_budget(
delegated_account: AccountSizeClass,
) -> u32 {
total_size_budget(&[
DLP_PROGRAM_DATA_SIZE_CLASS,
AccountSizeClass::Tiny, // payer
delegated_account, // delegated_account
AccountSizeClass::Tiny, // owner_program
AccountSizeClass::Tiny, // undelegation_request_pda
AccountSizeClass::Tiny, // delegation_record_pda
AccountSizeClass::Tiny, // delegation_metadata_pda
AccountSizeClass::Tiny, // system_program
])
}
104 changes: 90 additions & 14 deletions dlp-api/src/instruction_builder/undelegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use dlp::{
delegation_metadata_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,
Expand All @@ -26,6 +27,41 @@ pub fn undelegate(
delegated_account: Pubkey,
owner_program: Pubkey,
rent_reimbursement: Pubkey,
) -> Instruction {
build_undelegate(
validator,
delegated_account,
owner_program,
rent_reimbursement,
None,
)
}

/// Builds an undelegate instruction that also closes a matching request.
/// See [dlp::processor::process_undelegate] for docs.
#[allow(clippy::too_many_arguments)]
pub fn undelegate_with_request(
validator: Pubkey,
delegated_account: Pubkey,
owner_program: Pubkey,
rent_reimbursement: Pubkey,
request_rent_payer: Pubkey,
) -> Instruction {
build_undelegate(
validator,
delegated_account,
owner_program,
rent_reimbursement,
Some(request_rent_payer),
)
}

fn build_undelegate(
validator: Pubkey,
delegated_account: Pubkey,
owner_program: Pubkey,
rent_reimbursement: Pubkey,
request_rent_payer: Option<Pubkey>,
) -> Instruction {
let validator_compat = validator.compatize();
let delegated_account_compat = delegated_account.compatize();
Expand All @@ -49,22 +85,34 @@ pub fn undelegate(
let fees_vault_pda = fees_vault_pda().modernize();
let validator_fees_vault_pda =
validator_fees_vault_pda_from_validator(&validator_compat).modernize();
let mut accounts = vec![
AccountMeta::new(validator, true),
AccountMeta::new(delegated_account, false),
AccountMeta::new_readonly(owner_program, false),
AccountMeta::new(undelegate_buffer_pda, false),
AccountMeta::new_readonly(commit_state_pda, false),
AccountMeta::new_readonly(commit_record_pda, false),
AccountMeta::new(delegation_record_pda, false),
AccountMeta::new(delegation_metadata_pda, false),
AccountMeta::new(rent_reimbursement, false),
AccountMeta::new(fees_vault_pda, false),
AccountMeta::new(validator_fees_vault_pda, false),
AccountMeta::new_readonly(system_program::id(), false),
];

if let Some(request_rent_payer) = request_rent_payer {
let undelegation_request_pda =
undelegation_request_pda_from_delegated_account(
&delegated_account_compat,
)
.modernize();
accounts.push(AccountMeta::new(undelegation_request_pda, false));
accounts.push(AccountMeta::new(request_rent_payer, false));
}

Instruction {
program_id: dlp::id().modernize(),
accounts: vec![
AccountMeta::new(validator, true),
AccountMeta::new(delegated_account, false),
AccountMeta::new_readonly(owner_program, false),
AccountMeta::new(undelegate_buffer_pda, false),
AccountMeta::new_readonly(commit_state_pda, false),
AccountMeta::new_readonly(commit_record_pda, false),
AccountMeta::new(delegation_record_pda, false),
AccountMeta::new(delegation_metadata_pda, false),
AccountMeta::new(rent_reimbursement, false),
AccountMeta::new(fees_vault_pda, false),
AccountMeta::new(validator_fees_vault_pda, false),
AccountMeta::new_readonly(system_program::id(), false),
],
accounts,
data: DlpDiscriminator::Undelegate.to_vec(),
}
}
Expand All @@ -91,3 +139,31 @@ pub fn undelegate_size_budget(delegated_account: AccountSizeClass) -> u32 {
AccountSizeClass::Tiny, // system_program
])
}

///
/// Returns accounts-data-size budget for undelegate instruction with request
/// accounts.
///
/// This value can be used with ComputeBudgetInstruction::SetLoadedAccountsDataSizeLimit
///
pub fn undelegate_with_request_size_budget(
delegated_account: AccountSizeClass,
) -> u32 {
total_size_budget(&[
DLP_PROGRAM_DATA_SIZE_CLASS,
AccountSizeClass::Tiny, // validator
delegated_account, // delegated_account
AccountSizeClass::Tiny, // owner_program
delegated_account, // undelegate_buffer_pda
delegated_account, // commit_state_pda
AccountSizeClass::Tiny, // commit_record_pda
AccountSizeClass::Tiny, // delegation_record_pda
AccountSizeClass::Tiny, // delegation_metadata_pda
AccountSizeClass::Tiny, // rent_reimbursement
AccountSizeClass::Tiny, // fees_vault_pda
AccountSizeClass::Tiny, // validator_fees_vault_pda
AccountSizeClass::Tiny, // system_program
AccountSizeClass::Tiny, // undelegation_request_pda
AccountSizeClass::Tiny, // request_rent_payer
])
}
Loading
Loading