-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
solana: Refactor out
transfer_[ownership | token_authority]
into sep…
…arate files
- Loading branch information
Showing
3 changed files
with
354 additions
and
338 deletions.
There are no files selected for viewing
202 changes: 202 additions & 0 deletions
202
solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
use anchor_lang::prelude::*; | ||
use ntt_messages::chain_id::ChainId; | ||
|
||
use crate::{ | ||
config::Config, | ||
peer::NttManagerPeer, | ||
queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit, rate_limit::RateLimitState}, | ||
registered_transceiver::RegisteredTransceiver, | ||
}; | ||
|
||
pub mod transfer_ownership; | ||
pub mod transfer_token_authority; | ||
|
||
pub use transfer_ownership::*; | ||
pub use transfer_token_authority::*; | ||
|
||
// * Set peers | ||
|
||
#[derive(Accounts)] | ||
#[instruction(args: SetPeerArgs)] | ||
pub struct SetPeer<'info> { | ||
#[account(mut)] | ||
pub payer: Signer<'info>, | ||
|
||
pub owner: Signer<'info>, | ||
|
||
#[account( | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
#[account( | ||
init_if_needed, | ||
space = 8 + NttManagerPeer::INIT_SPACE, | ||
payer = payer, | ||
seeds = [NttManagerPeer::SEED_PREFIX, args.chain_id.id.to_be_bytes().as_ref()], | ||
bump | ||
)] | ||
pub peer: Account<'info, NttManagerPeer>, | ||
|
||
#[account( | ||
init_if_needed, | ||
space = 8 + InboxRateLimit::INIT_SPACE, | ||
payer = payer, | ||
seeds = [ | ||
InboxRateLimit::SEED_PREFIX, | ||
args.chain_id.id.to_be_bytes().as_ref() | ||
], | ||
bump, | ||
)] | ||
pub inbox_rate_limit: Account<'info, InboxRateLimit>, | ||
|
||
pub system_program: Program<'info, System>, | ||
} | ||
|
||
#[derive(AnchorDeserialize, AnchorSerialize)] | ||
pub struct SetPeerArgs { | ||
pub chain_id: ChainId, | ||
pub address: [u8; 32], | ||
pub limit: u64, | ||
/// The token decimals on the peer chain. | ||
pub token_decimals: u8, | ||
} | ||
|
||
pub fn set_peer(ctx: Context<SetPeer>, args: SetPeerArgs) -> Result<()> { | ||
ctx.accounts.peer.set_inner(NttManagerPeer { | ||
bump: ctx.bumps.peer, | ||
address: args.address, | ||
token_decimals: args.token_decimals, | ||
}); | ||
|
||
ctx.accounts.inbox_rate_limit.set_inner(InboxRateLimit { | ||
bump: ctx.bumps.inbox_rate_limit, | ||
rate_limit: RateLimitState::new(args.limit), | ||
}); | ||
Ok(()) | ||
} | ||
|
||
// * Register transceivers | ||
|
||
#[derive(Accounts)] | ||
pub struct RegisterTransceiver<'info> { | ||
#[account( | ||
mut, | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
pub owner: Signer<'info>, | ||
|
||
#[account(mut)] | ||
pub payer: Signer<'info>, | ||
|
||
#[account(executable)] | ||
/// CHECK: transceiver is meant to be a transceiver program. Arguably a `Program` constraint could be | ||
/// used here that wraps the Transceiver account type. | ||
pub transceiver: UncheckedAccount<'info>, | ||
|
||
#[account( | ||
init, | ||
space = 8 + RegisteredTransceiver::INIT_SPACE, | ||
payer = payer, | ||
seeds = [RegisteredTransceiver::SEED_PREFIX, transceiver.key().as_ref()], | ||
bump | ||
)] | ||
pub registered_transceiver: Account<'info, RegisteredTransceiver>, | ||
|
||
pub system_program: Program<'info, System>, | ||
} | ||
|
||
pub fn register_transceiver(ctx: Context<RegisterTransceiver>) -> Result<()> { | ||
let id = ctx.accounts.config.next_transceiver_id; | ||
ctx.accounts.config.next_transceiver_id += 1; | ||
ctx.accounts | ||
.registered_transceiver | ||
.set_inner(RegisteredTransceiver { | ||
bump: ctx.bumps.registered_transceiver, | ||
id, | ||
transceiver_address: ctx.accounts.transceiver.key(), | ||
}); | ||
|
||
ctx.accounts.config.enabled_transceivers.set(id, true)?; | ||
Ok(()) | ||
} | ||
|
||
// * Limit rate adjustment | ||
|
||
#[derive(Accounts)] | ||
pub struct SetOutboundLimit<'info> { | ||
#[account( | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
pub owner: Signer<'info>, | ||
|
||
#[account(mut)] | ||
pub rate_limit: Account<'info, OutboxRateLimit>, | ||
} | ||
|
||
#[derive(AnchorDeserialize, AnchorSerialize)] | ||
pub struct SetOutboundLimitArgs { | ||
pub limit: u64, | ||
} | ||
|
||
pub fn set_outbound_limit( | ||
ctx: Context<SetOutboundLimit>, | ||
args: SetOutboundLimitArgs, | ||
) -> Result<()> { | ||
ctx.accounts.rate_limit.set_limit(args.limit); | ||
Ok(()) | ||
} | ||
|
||
#[derive(Accounts)] | ||
#[instruction(args: SetInboundLimitArgs)] | ||
pub struct SetInboundLimit<'info> { | ||
#[account( | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
pub owner: Signer<'info>, | ||
|
||
#[account( | ||
mut, | ||
seeds = [ | ||
InboxRateLimit::SEED_PREFIX, | ||
args.chain_id.id.to_be_bytes().as_ref() | ||
], | ||
bump = rate_limit.bump | ||
)] | ||
pub rate_limit: Account<'info, InboxRateLimit>, | ||
} | ||
|
||
#[derive(AnchorDeserialize, AnchorSerialize)] | ||
pub struct SetInboundLimitArgs { | ||
pub limit: u64, | ||
pub chain_id: ChainId, | ||
} | ||
|
||
pub fn set_inbound_limit(ctx: Context<SetInboundLimit>, args: SetInboundLimitArgs) -> Result<()> { | ||
ctx.accounts.rate_limit.set_limit(args.limit); | ||
Ok(()) | ||
} | ||
|
||
// * Pausing | ||
|
||
#[derive(Accounts)] | ||
pub struct SetPaused<'info> { | ||
pub owner: Signer<'info>, | ||
|
||
#[account( | ||
mut, | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
} | ||
|
||
pub fn set_paused(ctx: Context<SetPaused>, paused: bool) -> Result<()> { | ||
ctx.accounts.config.paused = paused; | ||
Ok(()) | ||
} |
148 changes: 148 additions & 0 deletions
148
solana/programs/example-native-token-transfers/src/instructions/admin/transfer_ownership.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use anchor_lang::prelude::*; | ||
use wormhole_solana_utils::cpi::bpf_loader_upgradeable::{self, BpfLoaderUpgradeable}; | ||
|
||
#[cfg(feature = "idl-build")] | ||
use crate::messages::Hack; | ||
|
||
use crate::{config::Config, error::NTTError}; | ||
|
||
// * Transfer ownership | ||
|
||
/// For safety reasons, transferring ownership is a 2-step process. The first step is to set the | ||
/// new owner, and the second step is for the new owner to claim the ownership. | ||
/// This is to prevent a situation where the ownership is transferred to an | ||
/// address that is not able to claim the ownership (by mistake). | ||
/// | ||
/// The transfer can be cancelled by the existing owner invoking the [`claim_ownership`] | ||
/// instruction. | ||
/// | ||
/// Alternatively, the ownership can be transferred in a single step by calling the | ||
/// [`transfer_ownership_one_step_unchecked`] instruction. This can be dangerous because if the new owner | ||
/// cannot actually sign transactions (due to setting the wrong address), the program will be | ||
/// permanently locked. If the intention is to transfer ownership to a program using this instruction, | ||
/// take extra care to ensure that the owner is a PDA, not the program address itself. | ||
#[derive(Accounts)] | ||
pub struct TransferOwnership<'info> { | ||
#[account( | ||
mut, | ||
has_one = owner, | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
pub owner: Signer<'info>, | ||
|
||
/// CHECK: This account will be the signer in the [claim_ownership] instruction. | ||
new_owner: UncheckedAccount<'info>, | ||
|
||
#[account( | ||
seeds = [b"upgrade_lock"], | ||
bump, | ||
)] | ||
/// CHECK: The seeds constraint enforces that this is the correct address | ||
upgrade_lock: UncheckedAccount<'info>, | ||
|
||
#[account( | ||
mut, | ||
seeds = [crate::ID.as_ref()], | ||
bump, | ||
seeds::program = bpf_loader_upgradeable_program, | ||
)] | ||
program_data: Account<'info, ProgramData>, | ||
|
||
bpf_loader_upgradeable_program: Program<'info, BpfLoaderUpgradeable>, | ||
} | ||
|
||
pub fn transfer_ownership(ctx: Context<TransferOwnership>) -> Result<()> { | ||
ctx.accounts.config.pending_owner = Some(ctx.accounts.new_owner.key()); | ||
|
||
// TODO: only transfer authority when the authority is not already the upgrade lock | ||
bpf_loader_upgradeable::set_upgrade_authority_checked( | ||
CpiContext::new_with_signer( | ||
ctx.accounts | ||
.bpf_loader_upgradeable_program | ||
.to_account_info(), | ||
bpf_loader_upgradeable::SetUpgradeAuthorityChecked { | ||
program_data: ctx.accounts.program_data.to_account_info(), | ||
current_authority: ctx.accounts.owner.to_account_info(), | ||
new_authority: ctx.accounts.upgrade_lock.to_account_info(), | ||
}, | ||
&[&[b"upgrade_lock", &[ctx.bumps.upgrade_lock]]], | ||
), | ||
&crate::ID, | ||
) | ||
} | ||
|
||
pub fn transfer_ownership_one_step_unchecked(ctx: Context<TransferOwnership>) -> Result<()> { | ||
ctx.accounts.config.pending_owner = None; | ||
ctx.accounts.config.owner = ctx.accounts.new_owner.key(); | ||
|
||
// NOTE: unlike in `transfer_ownership`, we use the unchecked version of the | ||
// `set_upgrade_authority` instruction here. The checked version requires | ||
// the new owner to be a signer, which is what we want to avoid here. | ||
bpf_loader_upgradeable::set_upgrade_authority( | ||
CpiContext::new( | ||
ctx.accounts | ||
.bpf_loader_upgradeable_program | ||
.to_account_info(), | ||
bpf_loader_upgradeable::SetUpgradeAuthority { | ||
program_data: ctx.accounts.program_data.to_account_info(), | ||
current_authority: ctx.accounts.owner.to_account_info(), | ||
new_authority: Some(ctx.accounts.new_owner.to_account_info()), | ||
}, | ||
), | ||
&crate::ID, | ||
) | ||
} | ||
|
||
// * Claim ownership | ||
|
||
#[derive(Accounts)] | ||
pub struct ClaimOwnership<'info> { | ||
#[account( | ||
mut, | ||
constraint = ( | ||
config.pending_owner == Some(new_owner.key()) | ||
|| config.owner == new_owner.key() | ||
) @ NTTError::InvalidPendingOwner | ||
)] | ||
pub config: Account<'info, Config>, | ||
|
||
#[account( | ||
seeds = [b"upgrade_lock"], | ||
bump, | ||
)] | ||
/// CHECK: The seeds constraint enforces that this is the correct address | ||
upgrade_lock: UncheckedAccount<'info>, | ||
|
||
pub new_owner: Signer<'info>, | ||
|
||
#[account( | ||
mut, | ||
seeds = [crate::ID.as_ref()], | ||
bump, | ||
seeds::program = bpf_loader_upgradeable_program, | ||
)] | ||
program_data: Account<'info, ProgramData>, | ||
|
||
bpf_loader_upgradeable_program: Program<'info, BpfLoaderUpgradeable>, | ||
} | ||
|
||
pub fn claim_ownership(ctx: Context<ClaimOwnership>) -> Result<()> { | ||
ctx.accounts.config.pending_owner = None; | ||
ctx.accounts.config.owner = ctx.accounts.new_owner.key(); | ||
|
||
bpf_loader_upgradeable::set_upgrade_authority_checked( | ||
CpiContext::new_with_signer( | ||
ctx.accounts | ||
.bpf_loader_upgradeable_program | ||
.to_account_info(), | ||
bpf_loader_upgradeable::SetUpgradeAuthorityChecked { | ||
program_data: ctx.accounts.program_data.to_account_info(), | ||
current_authority: ctx.accounts.upgrade_lock.to_account_info(), | ||
new_authority: ctx.accounts.new_owner.to_account_info(), | ||
}, | ||
&[&[b"upgrade_lock", &[ctx.bumps.upgrade_lock]]], | ||
), | ||
&crate::ID, | ||
) | ||
} |
Oops, something went wrong.