Repository navigation
feat: pda upgrade poc #135
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: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| use crate::{ | ||
| contexts::{ | ||
| Initialize, ResetNonce, Unwhitelist, UpdateAuthority, UpdatePaused, UpdateTss, Whitelist, | ||
| AcceptAuthority, Initialize, MigratePda, ResetNonce, Unwhitelist, UpdateAuthority, | ||
| UpdatePaused, UpdateTss, Whitelist, | ||
| }, | ||
| errors::Errors, | ||
| state::InstructionId, | ||
| utils::{ | ||
| recover_and_verify_eth_address, validate_message_hash, verify_and_update_nonce, | ||
|
|
@@ -14,20 +16,24 @@ use anchor_lang::prelude::*; | |
| // Initializes the gateway PDA. | ||
| pub fn initialize(ctx: Context<Initialize>, tss_address: [u8; 20], chain_id: u64) -> Result<()> { | ||
| let initialized_pda = &mut ctx.accounts.pda; | ||
| let bump = ctx.bumps.pda; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets also add other issues that this might be closing, all issues like bump and authority that required pda upgrade to work i also think we can use this opportunity to put deposit fee in pda instead of constant and add posibility to update it |
||
|
|
||
| **initialized_pda = Pda { | ||
| nonce: 0, | ||
| tss_address, | ||
| authority: ctx.accounts.signer.key(), | ||
| chain_id, | ||
| deposit_paused: false, | ||
| bump, | ||
| nominated_authority: None, | ||
| }; | ||
|
|
||
| msg!( | ||
| "Gateway initialized: PDA authority = {}, chain_id = {}, TSS address = {:?}", | ||
| "Gateway initialized: PDA authority = {}, chain_id = {}, TSS address = {:?}, bump = {}", | ||
| ctx.accounts.signer.key(), | ||
| chain_id, | ||
| tss_address | ||
| tss_address, | ||
| bump | ||
| ); | ||
|
|
||
| Ok(()) | ||
|
|
@@ -50,14 +56,124 @@ pub fn update_tss(ctx: Context<UpdateTss>, tss_address: [u8; 20]) -> Result<()> | |
| Ok(()) | ||
| } | ||
|
|
||
| // Updates the PDA authority. Caller is authority stored in PDA. | ||
| // Migrates existing PDA to new format by reallocating account space. | ||
| // This is a one-time migration that can be called after program upgrade. | ||
| pub fn migrate_pda(ctx: Context<MigratePda>) -> Result<()> { | ||
| let pda_info = &ctx.accounts.pda.to_account_info(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dont we need verify_authority here as well? |
||
|
|
||
| // Check if already migrated by comparing account size | ||
| let current_size = pda_info.data_len(); | ||
| let new_size = 8 + std::mem::size_of::<Pda>(); | ||
|
|
||
| // If already migrated (size matches new size), do nothing | ||
| if current_size >= new_size { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need this check or it will be automatically checked by anchor somehow? |
||
| msg!("PDA already migrated, skipping"); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| // The realloc constraint in the context has already resized the account | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to make sure that pda with more data inside will contain enough funds for rent exempt? maybe that should be documented somewhere, even though it will probably have enough since deposits go to pda anywayrs |
||
| // Now we need to properly initialize the new fields | ||
| // We'll read the existing data, then write it back with new fields | ||
|
|
||
| // Find the bump seed from the seeds | ||
| let seeds = &[b"meta".as_ref()]; | ||
| let (_, bump) = Pubkey::find_program_address(seeds, ctx.program_id); | ||
|
|
||
| // Now we can safely deserialize the PDA (account is already resized) | ||
| // The new space will be uninitialized, but Anchor will handle it | ||
| let pda = &mut ctx.accounts.pda; | ||
|
|
||
| // Initialize new fields - they should be zero/uninitialized after realloc | ||
| // Set bump if it's not already set (should be 0 for old PDAs) | ||
| if pda.bump == 0 { | ||
| pda.bump = bump; | ||
| } | ||
| // Initialize nominated_authority to None if not set | ||
| if pda.nominated_authority.is_none() { | ||
| pda.nominated_authority = None; | ||
| } | ||
|
|
||
| msg!("PDA migrated: bump = {}, new_size = {}", bump, new_size); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we add nominated authority as well to log? |
||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| // Nominates a new authority (step 1 of 2-step transfer). | ||
| // Caller is current authority stored in PDA. | ||
| pub fn nominate_authority( | ||
| ctx: Context<UpdateAuthority>, | ||
| new_authority_address: Pubkey, | ||
| ) -> Result<()> { | ||
| verify_authority(&ctx.accounts.signer.key(), &ctx.accounts.pda)?; | ||
| let pda = &mut ctx.accounts.pda; | ||
|
|
||
| // Prevent self-nomination | ||
| require!( | ||
| new_authority_address != pda.authority, | ||
| Errors::InvalidAuthority | ||
| ); | ||
|
|
||
| pda.nominated_authority = Some(new_authority_address); | ||
|
|
||
| msg!( | ||
| "Authority nominated: nominated authority = {}, current authority = {}", | ||
| new_authority_address, | ||
| ctx.accounts.signer.key() | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| // Accepts authority nomination (step 2 of 2-step transfer). | ||
| // Caller is the nominated authority. | ||
| pub fn accept_authority(ctx: Context<AcceptAuthority>) -> Result<()> { | ||
| let pda = &mut ctx.accounts.pda; | ||
| let nominated = pda | ||
| .nominated_authority | ||
| .ok_or(Errors::NoNominatedAuthority)?; | ||
|
|
||
| // Verify caller is the nominated authority | ||
| require!( | ||
| ctx.accounts.new_authority.key() == nominated, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit but maybe we can use |
||
| Errors::InvalidAuthority | ||
| ); | ||
|
|
||
| let old_authority = pda.authority; | ||
| pda.authority = nominated; | ||
| pda.nominated_authority = None; | ||
|
|
||
| msg!( | ||
| "Authority accepted: new authority = {}, previous authority = {}", | ||
| nominated, | ||
| old_authority | ||
| ); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| // Cancels authority nomination (optional safety feature). | ||
| // Caller is current authority stored in PDA. | ||
| pub fn cancel_authority_nomination(ctx: Context<UpdateAuthority>) -> Result<()> { | ||
| verify_authority(&ctx.accounts.signer.key(), &ctx.accounts.pda)?; | ||
| let pda = &mut ctx.accounts.pda; | ||
| pda.nominated_authority = None; | ||
|
|
||
| msg!("Authority nomination cancelled by current authority"); | ||
| Ok(()) | ||
| } | ||
|
|
||
| // Legacy function: Updates the PDA authority in one step (deprecated). | ||
| // Caller is authority stored in PDA. | ||
| // This is kept for backward compatibility but should use 2-step process. | ||
| pub fn update_authority( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we can consider removing this one as it is redundant to reduce program size but up to you |
||
| ctx: Context<UpdateAuthority>, | ||
| new_authority_address: Pubkey, | ||
| ) -> Result<()> { | ||
| verify_authority(&ctx.accounts.signer.key(), &ctx.accounts.pda)?; | ||
| let pda = &mut ctx.accounts.pda; | ||
| pda.authority = new_authority_address; | ||
| // Clear any existing nomination | ||
| pda.nominated_authority = None; | ||
|
|
||
| msg!( | ||
| "PDA authority updated: new authority = {}, previous authority = {}", | ||
|
|
||
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.
can len as usize be used here, eg https://github.com/solana-developers/anchor-zero-copy-example/blob/main/programs/zero-copy/src/lib.rs#L117