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
62 changes: 48 additions & 14 deletions programs/gateway/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct Initialize<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(init, payer = signer, space = size_of::<Pda>() + 8, seeds = [b"meta"], bump)]
#[account(init, payer = signer, space = 8 + size_of::<Pda>(), seeds = [b"meta"], bump)]
pub pda: Account<'info, Pda>,

/// The system program.
Expand All @@ -27,7 +27,7 @@ pub struct Execute<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The destination program.
Expand All @@ -53,7 +53,7 @@ pub struct IncrementNonce<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}

Expand All @@ -65,7 +65,7 @@ pub struct Deposit<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The system program.
Expand All @@ -80,7 +80,7 @@ pub struct DepositSplToken<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The whitelist entry account for the SPL token.
Expand Down Expand Up @@ -121,7 +121,7 @@ pub struct Withdraw<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The recipient account for the withdrawn SOL.
Expand All @@ -138,7 +138,7 @@ pub struct WithdrawSPLToken<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The associated token account for the Gateway PDA.
Expand Down Expand Up @@ -174,7 +174,7 @@ pub struct ExecuteSPLToken<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The associated token account for the Gateway PDA.
Expand Down Expand Up @@ -221,7 +221,7 @@ pub struct UpdateTss<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}

Expand All @@ -233,7 +233,7 @@ pub struct UpdateAuthority<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}

Expand All @@ -245,7 +245,7 @@ pub struct ResetNonce<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}

Expand All @@ -257,7 +257,7 @@ pub struct UpdatePaused<'info> {
pub signer: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}

Expand All @@ -269,7 +269,7 @@ pub struct Whitelist<'info> {
pub authority: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The whitelist entry account being initialized.
Expand Down Expand Up @@ -297,7 +297,7 @@ pub struct Unwhitelist<'info> {
pub authority: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump)]
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,

/// The whitelist entry account being closed.
Expand All @@ -319,3 +319,37 @@ pub struct Upgrade<'info> {
/// The account of the signer checking the upgrade
pub signer: Signer<'info>,
}

/// Instruction context for migrating PDA to new format.
#[derive(Accounts)]
pub struct MigratePda<'info> {
/// The account paying for the migration.
#[account(mut)]
pub payer: Signer<'info>,

/// Gateway PDA to be migrated.
#[account(
mut,
seeds = [b"meta"],
bump,
realloc = 8 + size_of::<Pda>(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

realloc::payer = payer,
realloc::zero = false,
)]
pub pda: Account<'info, Pda>,

/// The system program.
pub system_program: Program<'info, System>,
}

/// Instruction context for accepting authority nomination.
#[derive(Accounts)]
pub struct AcceptAuthority<'info> {
/// The nominated authority accepting the transfer.
#[account(mut)]
pub new_authority: Signer<'info>,

/// Gateway PDA.
#[account(mut, seeds = [b"meta"], bump = pda.bump)]
pub pda: Account<'info, Pda>,
}
4 changes: 4 additions & 0 deletions programs/gateway/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ pub enum Errors {
InvalidInstructionData,
#[msg("InvalidAtaOwner")]
InvalidAtaOwner,
#[msg("NoNominatedAuthority")]
NoNominatedAuthority,
#[msg("InvalidAuthority")]
InvalidAuthority,
}
124 changes: 120 additions & 4 deletions programs/gateway/src/instructions/admin.rs
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,
Expand All @@ -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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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(())
Expand All @@ -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();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit but maybe we can use signer for naming to be consistent

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(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 = {}",
Expand Down
2 changes: 1 addition & 1 deletion programs/gateway/src/instructions/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ fn handle_spl_token_common(

// 3. Transfer tokens
let token = &ctx.accounts.token_program;
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]];
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[pda.bump]]];

let xfer_ctx = CpiContext::new_with_signer(
token.to_account_info(),
Expand Down
2 changes: 1 addition & 1 deletion programs/gateway/src/instructions/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ pub fn handle_spl(

// 4. Transfer tokens
let token = &ctx.accounts.token_program;
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]];
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[pda.bump]]];

let xfer_ctx = CpiContext::new_with_signer(
token.to_account_info(),
Expand Down
Loading