From 4e9338635f286a70623d72ebd2b799d18728505f Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 6 Dec 2024 19:59:34 -0500 Subject: [PATCH] solana[WIP]: Refactor to reduce code duplication --- .../src/instructions/initialize.rs | 83 ++++++++--- .../src/instructions/mod.rs | 4 - .../src/instructions/release_inbound.rs | 137 +++++++++++++++--- .../example-native-token-transfers/src/lib.rs | 20 +-- 4 files changed, 196 insertions(+), 48 deletions(-) diff --git a/solana/programs/example-native-token-transfers/src/instructions/initialize.rs b/solana/programs/example-native-token-transfers/src/instructions/initialize.rs index bcc37de0d..bb628306a 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/initialize.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/initialize.rs @@ -37,12 +37,7 @@ pub struct Initialize<'info> { )] pub config: Box>, - #[account( - constraint = - args.mode == Mode::Locking - || mint.mint_authority.unwrap() == token_authority.key() - @ NTTError::InvalidMintAuthority, - )] + #[account()] pub mint: Box>, #[account( @@ -95,25 +90,79 @@ pub struct InitializeArgs { pub mode: ntt_messages::mode::Mode, } -pub fn initialize(ctx: Context, args: InitializeArgs) -> 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(), +#[derive(Accounts)] +#[instruction(args: InitializeArgs)] +pub struct InitializeDefault<'info> { + #[account( + constraint = + args.mode == Mode::Locking + || common.mint.mint_authority.unwrap() == common.token_authority.key() + @ NTTError::InvalidMintAuthority, + )] + pub common: Initialize<'info>, +} + +pub fn initialize(ctx: Context, args: InitializeArgs) -> Result<()> { + initialize_config_and_rate_limit( + &mut ctx.accounts.common, + ctx.bumps.common.config, + args.chain_id, + args.limit, + args.mode, + ) +} + +#[derive(Accounts)] +#[instruction(args: InitializeArgs)] +pub struct InitializeMultisig<'info> { + #[account( + constraint = + args.mode == Mode::Locking + || common.mint.mint_authority.unwrap() == multisig.key() + @ NTTError::InvalidMintAuthority, + )] + pub common: Initialize<'info>, + + #[account()] + /// CHECK: multisig is mint authority + pub multisig: UncheckedAccount<'info>, +} + +pub fn initialize_multisig(ctx: Context, args: InitializeArgs) -> Result<()> { + initialize_config_and_rate_limit( + &mut ctx.accounts.common, + ctx.bumps.common.config, + args.chain_id, + args.limit, + args.mode, + ) +} + +fn initialize_config_and_rate_limit( + common: &mut Initialize<'_>, + config_bump: u8, + chain_id: u16, + limit: u64, + mode: ntt_messages::mode::Mode, +) -> Result<()> { + common.config.set_inner(crate::config::Config { + bump: config_bump, + mint: common.mint.key(), + token_program: common.token_program.key(), + mode, + chain_id: ChainId { id: chain_id }, + owner: common.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(), + custody: common.custody.key(), }); - ctx.accounts.rate_limit.set_inner(OutboxRateLimit { - rate_limit: RateLimitState::new(args.limit), + common.rate_limit.set_inner(OutboxRateLimit { + rate_limit: RateLimitState::new(limit), }); Ok(()) diff --git a/solana/programs/example-native-token-transfers/src/instructions/mod.rs b/solana/programs/example-native-token-transfers/src/instructions/mod.rs index 5209f70a5..d9fec7be5 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/mod.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/mod.rs @@ -1,19 +1,15 @@ 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::*; diff --git a/solana/programs/example-native-token-transfers/src/instructions/release_inbound.rs b/solana/programs/example-native-token-transfers/src/instructions/release_inbound.rs index d56a51e70..603cbf4ab 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/release_inbound.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/release_inbound.rs @@ -1,3 +1,5 @@ +use std::ops::{Deref, DerefMut}; + use anchor_lang::prelude::*; use anchor_spl::token_interface; use ntt_messages::mode::Mode; @@ -66,6 +68,34 @@ pub struct ReleaseInboundMint<'info> { common: ReleaseInbound<'info>, } +impl<'info> Deref for ReleaseInboundMint<'info> { + type Target = ReleaseInbound<'info>; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl Deref for ReleaseInboundMintBumps { + type Target = ReleaseInboundBumps; + + fn deref(&self) -> &Self::Target { + &self.common + } +} + +impl<'info> DerefMut for ReleaseInboundMint<'info> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.common + } +} + +#[derive(Accounts)] +pub struct ReleaseInboundMintDefault<'info> { + #[account(constraint = common.mint.mint_authority.unwrap() == common.token_authority.key())] + common: ReleaseInboundMint<'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 @@ -74,23 +104,9 @@ pub struct ReleaseInboundMint<'info> { /// together with [`crate::instructions::redeem`] in a transaction, so that the minting /// is attempted optimistically. pub fn release_inbound_mint<'info>( - ctx: Context<'_, '_, '_, 'info, ReleaseInboundMint<'info>>, + ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintDefault<'info>>, args: ReleaseInboundArgs, ) -> 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 @@ -120,7 +136,7 @@ pub fn release_inbound_mint<'info>( &[ctx.bumps.common.token_authority], ]], ), - inbox_item.amount, + ctx.accounts.common.inbox_item.amount, )?; // Step 2: transfer the tokens from the custody account to the recipient @@ -131,13 +147,100 @@ pub fn release_inbound_mint<'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.inbox_item.amount, + ctx.accounts.common.mint.decimals, + &[&[ + crate::TOKEN_AUTHORITY_SEED, + &[ctx.bumps.common.token_authority], + ]], + )?; + + release_inbox_item(&mut ctx.accounts.common.inbox_item, args.revert_on_delay) +} + +#[derive(Accounts)] +pub struct ReleaseInboundMintMultisig<'info> { + #[account(constraint = common.mint.mint_authority.unwrap() == multisig.key())] + common: ReleaseInboundMint<'info>, + + /// CHECK: multisig account should be mint authority + pub multisig: UncheckedAccount<'info>, +} + +pub fn release_inbound_mint_multisig<'info>( + ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintMultisig<'info>>, + args: ReleaseInboundArgs, +) -> Result<()> { + // 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.multisig.key(), + &[&ctx.accounts.common.token_authority.key()], + ctx.accounts.common.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.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, + ctx.accounts.common.inbox_item.amount, ctx.accounts.common.mint.decimals, &[&[ crate::TOKEN_AUTHORITY_SEED, &[ctx.bumps.common.token_authority], ]], )?; + + release_inbox_item(&mut ctx.accounts.common.inbox_item, args.revert_on_delay) +} + +fn release_inbox_item(inbox_item: &mut InboxItem, revert_on_delay: bool) -> Result<()> { + let released = inbox_item.try_release()?; + + if !released { + if revert_on_delay { + return Err(NTTError::CantReleaseYet.into()); + } else { + return Ok(()); + } + } + + assert!(inbox_item.release_status == ReleaseStatus::Released); + Ok(()) } diff --git a/solana/programs/example-native-token-transfers/src/lib.rs b/solana/programs/example-native-token-transfers/src/lib.rs index dc99a92d8..3b2a69c04 100644 --- a/solana/programs/example-native-token-transfers/src/lib.rs +++ b/solana/programs/example-native-token-transfers/src/lib.rs @@ -70,13 +70,13 @@ pub mod example_native_token_transfers { use super::*; - pub fn initialize(ctx: Context, args: InitializeArgs) -> Result<()> { + pub fn initialize(ctx: Context, args: InitializeArgs) -> Result<()> { instructions::initialize(ctx, args) } pub fn initialize_multisig( ctx: Context, - args: InitializeMultisigArgs, + args: InitializeArgs, ) -> Result<()> { instructions::initialize_multisig(ctx, args) } @@ -108,24 +108,24 @@ pub mod example_native_token_transfers { } pub fn release_inbound_mint<'info>( - ctx: Context<'_, '_, '_, 'info, ReleaseInboundMint<'info>>, + ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintDefault<'info>>, args: ReleaseInboundArgs, ) -> Result<()> { instructions::release_inbound_mint(ctx, args) } - pub fn release_inbound_unlock<'info>( - ctx: Context<'_, '_, '_, 'info, ReleaseInboundUnlock<'info>>, + pub fn release_inbound_mint_multisig<'info>( + ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintMultisig<'info>>, args: ReleaseInboundArgs, ) -> Result<()> { - instructions::release_inbound_unlock(ctx, args) + instructions::release_inbound_mint_multisig(ctx, args) } - pub fn release_inbound_multisig_mint<'info>( - ctx: Context<'_, '_, '_, 'info, ReleaseInboundMultisigMint<'info>>, - args: ReleaseInboundMultisigArgs, + pub fn release_inbound_unlock<'info>( + ctx: Context<'_, '_, '_, 'info, ReleaseInboundUnlock<'info>>, + args: ReleaseInboundArgs, ) -> Result<()> { - instructions::release_inbound_multisig_mint(ctx, args) + instructions::release_inbound_unlock(ctx, args) } pub fn transfer_ownership(ctx: Context) -> Result<()> {