Skip to content

Commit

Permalink
solana: Add multisig versions of initialize and release_inbound_mint
Browse files Browse the repository at this point in the history
  • Loading branch information
nvsriram committed Dec 3, 2024
1 parent 738c67b commit b041c79
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use anchor_lang::prelude::*;
use anchor_spl::{associated_token::AssociatedToken, token_interface};
use ntt_messages::{chain_id::ChainId, mode::Mode};
use wormhole_solana_utils::cpi::bpf_loader_upgradeable::BpfLoaderUpgradeable;

#[cfg(feature = "idl-build")]
use crate::messages::Hack;

use crate::{
bitmap::Bitmap,
error::NTTError,
queue::{outbox::OutboxRateLimit, rate_limit::RateLimitState},
};

#[derive(Accounts)]
#[instruction(args: InitializeMultisigArgs)]
pub struct InitializeMultisig<'info> {
#[account(mut)]
pub payer: Signer<'info>,

#[account(address = program_data.upgrade_authority_address.unwrap_or_default())]
pub deployer: Signer<'info>,

#[account(
seeds = [crate::ID.as_ref()],
bump,
seeds::program = bpf_loader_upgradeable_program,
)]
program_data: Account<'info, ProgramData>,

#[account(
init,
space = 8 + crate::config::Config::INIT_SPACE,
payer = payer,
seeds = [crate::config::Config::SEED_PREFIX],
bump
)]
pub config: Box<Account<'info, crate::config::Config>>,

#[account(
constraint =
args.mode == Mode::Locking
|| mint.mint_authority.unwrap() == multisig.key()
@ NTTError::InvalidMintAuthority,
)]
pub mint: Box<InterfaceAccount<'info, token_interface::Mint>>,

#[account(
init,
payer = payer,
space = 8 + OutboxRateLimit::INIT_SPACE,
seeds = [OutboxRateLimit::SEED_PREFIX],
bump,
)]
pub rate_limit: Account<'info, OutboxRateLimit>,

#[account()]
/// CHECK: multisig is mint authority
pub multisig: UncheckedAccount<'info>,

#[account(
seeds = [crate::TOKEN_AUTHORITY_SEED],
bump,
)]
/// CHECK: [`token_authority`] is checked against the custody account and the [`mint`]'s mint_authority
/// In any case, this function is used to set the Config and initialize the program so we
/// assume the caller of this function will have total control over the program.
///
/// TODO: Using `UncheckedAccount` here leads to "Access violation in stack frame ...".
/// Could refactor code to use `Box<_>` to reduce stack size.
pub token_authority: AccountInfo<'info>,

#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = token_authority,
associated_token::token_program = token_program,
)]
/// The custody account that holds tokens in locking mode and temporarily
/// holds tokens in burning mode.
/// CHECK: Use init_if_needed here to prevent a denial-of-service of the [`initialize`]
/// function if the token account has already been created.
pub custody: InterfaceAccount<'info, token_interface::TokenAccount>,

/// CHECK: checked to be the appropriate token program when initialising the
/// associated token account for the given mint.
pub token_program: Interface<'info, token_interface::TokenInterface>,
pub associated_token_program: Program<'info, AssociatedToken>,
bpf_loader_upgradeable_program: Program<'info, BpfLoaderUpgradeable>,

system_program: Program<'info, System>,
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct InitializeMultisigArgs {
pub chain_id: u16,
pub limit: u64,
pub mode: ntt_messages::mode::Mode,
}

pub fn initialize_multisig(
ctx: Context<InitializeMultisig>,
args: InitializeMultisigArgs,
) -> Result<()> {
ctx.accounts.config.set_inner(crate::config::Config {
bump: ctx.bumps.config,
mint: ctx.accounts.mint.key(),
token_program: ctx.accounts.token_program.key(),
mode: args.mode,
chain_id: ChainId { id: args.chain_id },
owner: ctx.accounts.deployer.key(),
pending_owner: None,
paused: false,
next_transceiver_id: 0,
// NOTE: can't be changed for now
threshold: 1,
enabled_transceivers: Bitmap::new(),
custody: ctx.accounts.custody.key(),
});

ctx.accounts.rate_limit.set_inner(OutboxRateLimit {
rate_limit: RateLimitState::new(args.limit),
});

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
pub mod admin;
pub mod initialize;
pub mod initialize_multisig;
pub mod luts;
pub mod mark_outbox_item_as_released;
pub mod redeem;
pub mod release_inbound;
pub mod release_inbound_multisig;
pub mod transfer;

pub use admin::*;
pub use initialize::*;
pub use initialize_multisig::*;
pub use luts::*;
pub use mark_outbox_item_as_released::*;
pub use redeem::*;
pub use release_inbound::*;
pub use release_inbound_multisig::*;
pub use transfer::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use anchor_lang::prelude::*;
use anchor_spl::token_interface;
use ntt_messages::mode::Mode;
use spl_token_2022::onchain;

use crate::{
config::*,
error::NTTError,
queue::inbox::{InboxItem, ReleaseStatus},
};

#[derive(Accounts)]
pub struct ReleaseInboundMultisig<'info> {
#[account(mut)]
pub payer: Signer<'info>,

pub config: NotPausedConfig<'info>,

#[account(mut)]
pub inbox_item: Account<'info, InboxItem>,

#[account(
mut,
associated_token::authority = inbox_item.recipient_address,
associated_token::mint = mint,
associated_token::token_program = token_program,
)]
pub recipient: InterfaceAccount<'info, token_interface::TokenAccount>,

/// CHECK: multisig account should be mint authority
#[account(constraint = mint.mint_authority.unwrap() == multisig.key())]
pub multisig: UncheckedAccount<'info>,

#[account(
seeds = [crate::TOKEN_AUTHORITY_SEED],
bump,
)]
/// CHECK The seeds constraint ensures that this is the correct address
pub token_authority: UncheckedAccount<'info>,

#[account(
mut,
address = config.mint,
)]
/// CHECK: the mint address matches the config
pub mint: InterfaceAccount<'info, token_interface::Mint>,

pub token_program: Interface<'info, token_interface::TokenInterface>,

/// CHECK: the token program checks if this indeed the right authority for the mint
#[account(
mut,
address = config.custody
)]
pub custody: InterfaceAccount<'info, token_interface::TokenAccount>,
}

#[derive(AnchorDeserialize, AnchorSerialize)]
pub struct ReleaseInboundMultisigArgs {
pub revert_on_delay: bool,
}

// Burn/mint

#[derive(Accounts)]
pub struct ReleaseInboundMultisigMint<'info> {
#[account(
constraint = common.config.mode == Mode::Burning @ NTTError::InvalidMode,
)]
common: ReleaseInboundMultisig<'info>,
}

/// Release an inbound transfer and mint the tokens to the recipient.
/// When `revert_on_error` is true, the transaction will revert if the
/// release timestamp has not been reached. When `revert_on_error` is false, the
/// transaction succeeds, but the minting is not performed.
/// Setting this flag to `false` is useful when bundling this instruction
/// together with [`crate::instructions::redeem`] in a transaction, so that the minting
/// is attempted optimistically.
pub fn release_inbound_multisig_mint<'info>(
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMultisigMint<'info>>,
args: ReleaseInboundMultisigArgs,
) -> Result<()> {
let inbox_item = &mut ctx.accounts.common.inbox_item;

let released = inbox_item.try_release()?;

if !released {
if args.revert_on_delay {
return Err(NTTError::CantReleaseYet.into());
} else {
return Ok(());
}
}

assert!(inbox_item.release_status == ReleaseStatus::Released);

// NOTE: minting tokens is a two-step process:
// 1. Mint tokens to the custody account
// 2. Transfer the tokens from the custody account to the recipient
//
// This is done to ensure that if the token has a transfer hook defined, it
// will be called after the tokens are minted.
// Unfortunately the Token2022 program doesn't trigger transfer hooks when
// minting tokens, so we have to do it "manually" via a transfer.
//
// If we didn't do this, transfer hooks could be bypassed by transferring
// the tokens out through NTT first, then back in to the intended recipient.
//
// The [`transfer_burn`] function operates in a similar way
// (transfer to custody from sender, *then* burn).

// Step 1: mint tokens to the custody account
let ix = spl_token_2022::instruction::mint_to(
&ctx.accounts.common.token_program.key(),
&ctx.accounts.common.mint.key(),
&ctx.accounts.common.custody.key(),
&ctx.accounts.common.multisig.key(),
&[&ctx.accounts.common.token_authority.key()],
inbox_item.amount,
)?;
solana_program::program::invoke_signed(
&ix,
&[
ctx.accounts.common.custody.to_account_info(),
ctx.accounts.common.mint.to_account_info(),
ctx.accounts.common.token_authority.to_account_info(),
ctx.accounts.common.multisig.to_account_info(),
],
&[&[
crate::TOKEN_AUTHORITY_SEED,
&[ctx.bumps.common.token_authority],
]],
)?;

// Step 2: transfer the tokens from the custody account to the recipient
onchain::invoke_transfer_checked(
&ctx.accounts.common.token_program.key(),
ctx.accounts.common.custody.to_account_info(),
ctx.accounts.common.mint.to_account_info(),
ctx.accounts.common.recipient.to_account_info(),
ctx.accounts.common.token_authority.to_account_info(),
ctx.remaining_accounts,
inbox_item.amount,
ctx.accounts.common.mint.decimals,
&[&[
crate::TOKEN_AUTHORITY_SEED,
&[ctx.bumps.common.token_authority],
]],
)?;
Ok(())
}
14 changes: 14 additions & 0 deletions solana/programs/example-native-token-transfers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ pub mod example_native_token_transfers {
instructions::initialize(ctx, args)
}

pub fn initialize_multisig(
ctx: Context<InitializeMultisig>,
args: InitializeMultisigArgs,
) -> Result<()> {
instructions::initialize_multisig(ctx, args)
}

pub fn initialize_lut(ctx: Context<InitializeLUT>, recent_slot: u64) -> Result<()> {
instructions::initialize_lut(ctx, recent_slot)
}
Expand Down Expand Up @@ -114,6 +121,13 @@ pub mod example_native_token_transfers {
instructions::release_inbound_unlock(ctx, args)
}

pub fn release_inbound_multisig_mint<'info>(
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMultisigMint<'info>>,
args: ReleaseInboundMultisigArgs,
) -> Result<()> {
instructions::release_inbound_multisig_mint(ctx, args)
}

pub fn transfer_ownership(ctx: Context<TransferOwnership>) -> Result<()> {
instructions::transfer_ownership(ctx)
}
Expand Down

0 comments on commit b041c79

Please sign in to comment.