-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add an instruction which allows to undelegate confined accounts #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR introduces support for a new undelegate instruction for confined accounts. It adds a new discriminator variant, an instruction builder function, processor logic to validate admin authority and manage PDAs, and an integration test. The changes integrate the new instruction into the dispatch pipeline and processor chain. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
src/discriminator.rs(1 hunks)src/instruction_builder/mod.rs(2 hunks)src/instruction_builder/undelegate_confined_account.rs(1 hunks)src/lib.rs(1 hunks)src/processor/fast/mod.rs(2 hunks)src/processor/fast/undelegate.rs(1 hunks)src/processor/fast/undelegate_confined_account.rs(1 hunks)tests/test_undelegate_confined_account.rs(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-15T11:45:25.802Z
Learnt from: snawaz
Repo: magicblock-labs/delegation-program PR: 107
File: src/entrypoint.rs:141-144
Timestamp: 2025-10-15T11:45:25.802Z
Learning: In the delegation-program codebase, prefer using `log!` (from pinocchio_log) over `msg!` for error and panic scenarios in the entrypoint code, as per maintainer preference.
Applied to files:
src/processor/fast/mod.rs
🧬 Code graph analysis (5)
src/lib.rs (1)
src/processor/fast/undelegate_confined_account.rs (1)
process_undelegate_confined_account(32-171)
src/instruction_builder/mod.rs (1)
src/instruction_builder/undelegate_confined_account.rs (1)
undelegate_confined_account(14-40)
src/processor/fast/mod.rs (1)
src/instruction_builder/undelegate_confined_account.rs (1)
undelegate_confined_account(14-40)
tests/test_undelegate_confined_account.rs (3)
src/pda.rs (2)
delegation_metadata_pda_from_delegated_account(106-112)delegation_record_pda_from_delegated_account(98-104)tests/fixtures/accounts.rs (2)
create_delegation_record_data(67-84)get_delegation_metadata_data(99-105)src/instruction_builder/undelegate_confined_account.rs (1)
undelegate_confined_account(14-40)
src/instruction_builder/undelegate_confined_account.rs (1)
src/pda.rs (3)
delegation_metadata_pda_from_delegated_account(106-112)delegation_record_pda_from_delegated_account(98-104)undelegate_buffer_pda_from_delegated_account(141-147)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint
🔇 Additional comments (10)
src/processor/fast/mod.rs (1)
8-8: LGTM!The new module declaration and re-export follow the established pattern for processor modules.
Also applies to: 18-18
src/lib.rs (1)
120-122: LGTM!The new discriminator case correctly routes to the fast processor handler, consistent with other fast-path instructions.
src/instruction_builder/mod.rs (1)
16-16: LGTM!Module declaration and re-export follow the established pattern and maintain alphabetical ordering.
Also applies to: 35-35
src/discriminator.rs (1)
42-43: LGTM!The new discriminator variant follows the established pattern with unique value and consistent documentation style.
src/processor/fast/undelegate.rs (1)
232-232: LGTM!Appropriate visibility change to
pub(crate)enables code reuse by the newprocess_undelegate_confined_accounthandler while keeping the function internal to the crate.src/instruction_builder/undelegate_confined_account.rs (1)
14-39: LGTM!The instruction builder correctly:
- Derives all required PDAs from the delegated account
- Sets appropriate mutability flags (writable for accounts being modified/closed)
- Maintains correct account ordering matching the processor expectation
- Uses the new discriminator for serialization
src/processor/fast/undelegate_confined_account.rs (4)
22-41: LGTM!The function signature and account unpacking follow standard patterns. Documentation clearly describes the required accounts and their roles.
43-44: LGTM!Admin signer validation is correctly implemented.
82-109: LGTM!The validation logic correctly enforces that:
- The delegated account is a valid PDA owned by the delegation program
- Delegation record and metadata are initialized
- The account is confined (authority == system program)
- The owner matches the stored owner in the delegation record
146-170: LGTM!The CPI processing and cleanup logic is correctly implemented:
- Reuses the existing
process_undelegation_with_cpihelper for consistency- Properly drops borrowed data references before closing PDAs to avoid memory safety issues
- Closes all PDAs in the correct order, returning lamports to the admin
The error handling is safe due to Solana's transaction atomicity - if any step fails, all state changes are reverted.
| // Verify admin is the program upgrade authority. | ||
| // In unit tests, accept DEFAULT_VALIDATOR_IDENTITY without requiring ProgramData. | ||
| #[cfg(feature = "unit_test_config")] | ||
| { | ||
| if !pubkey_eq( | ||
| &crate::consts::DEFAULT_VALIDATOR_IDENTITY.to_bytes(), | ||
| admin.key(), | ||
| ) { | ||
| return Err(DlpError::Unauthorized.into()); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(not(feature = "unit_test_config"))] | ||
| { | ||
| // Deserialize UpgradeableLoaderState::ProgramData to get upgrade_authority_address | ||
| let data = delegation_program_data.try_borrow_data()?; | ||
| // Use the same structure as solana_program to decode ProgramData | ||
| use solana_program::bpf_loader_upgradeable::UpgradeableLoaderState as SolUpgradeableLoaderState; | ||
| let state: SolUpgradeableLoaderState = | ||
| bincode::deserialize(&data).map_err(|_| ProgramError::InvalidAccountData)?; | ||
| if let SolUpgradeableLoaderState::ProgramData { | ||
| upgrade_authority_address, | ||
| .. | ||
| } = state | ||
| { | ||
| let Some(auth) = upgrade_authority_address else { | ||
| return Err(DlpError::Unauthorized.into()); | ||
| }; | ||
| if !pubkey_eq(&auth.to_bytes(), admin.key()) { | ||
| return Err(DlpError::Unauthorized.into()); | ||
| } | ||
| } else { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n src/processor/fast/undelegate_confined_account.rs | head -120Repository: magicblock-labs/delegation-program
Length of output: 5608
🏁 Script executed:
rg -n "ProgramData|find_program_address" --type=rust src/ | head -50Repository: magicblock-labs/delegation-program
Length of output: 3780
🏁 Script executed:
sed -n '190,230p' src/processor/utils/loaders.rsRepository: magicblock-labs/delegation-program
Length of output: 1488
🏁 Script executed:
cat -n src/instruction_builder/undelegate_confined_account.rsRepository: magicblock-labs/delegation-program
Length of output: 2084
Add validation that delegation_program_data matches this program's actual ProgramData account.
The code deserializes the UpgradeableLoaderState and checks the upgrade authority, but does not validate that delegation_program_data (account 7) is actually the ProgramData account for this program. An attacker could pass a different program's ProgramData account with a different upgrade authority to bypass the authorization check.
A validation pattern for this check already exists in src/processor/utils/loaders.rs (line 209-213). Add the following check before deserializing:
let program_data_address =
Pubkey::find_program_address(&[_program_id.as_ref()], &solana_program::bpf_loader_upgradeable::id()).0;
if !pubkey_eq(&program_data_address.to_bytes(), delegation_program_data.key()) {
return Err(ProgramError::InvalidAccountData);
}🤖 Prompt for AI Agents
In src/processor/fast/undelegate_confined_account.rs around lines 46 to 80, the
code deserializes delegation_program_data and checks only the upgrade authority
but does not ensure delegation_program_data is actually this program's
ProgramData account; before deserializing, compute the expected program_data PDA
via Pubkey::find_program_address with this program_id and
solana_program::bpf_loader_upgradeable::id(), compare its bytes to
delegation_program_data.key(), and if they differ return
ProgramError::InvalidAccountData; then proceed to deserialize and validate the
upgrade_authority as currently implemented.
Description
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.