diff --git a/solana/programs/token-bridge/src/legacy/processor/attest_token.rs b/solana/programs/token-bridge/src/legacy/processor/attest_token.rs index aef3464893..e69272d171 100644 --- a/solana/programs/token-bridge/src/legacy/processor/attest_token.rs +++ b/solana/programs/token-bridge/src/legacy/processor/attest_token.rs @@ -1,4 +1,6 @@ -use crate::{legacy::instruction::LegacyAttestTokenArgs, utils, zero_copy::Mint}; +use crate::{ + error::TokenBridgeError, legacy::instruction::LegacyAttestTokenArgs, utils, zero_copy::Mint, +}; use anchor_lang::prelude::*; use anchor_spl::metadata; use core_bridge_program::sdk::{self as core_bridge_sdk, LoadZeroCopy}; @@ -148,7 +150,13 @@ impl<'info> AttestToken<'info> { fn constraints(ctx: &Context) -> Result<()> { // Make sure the mint authority is not the Token Bridge's. If it is, then this mint // originated from a foreign network. - crate::utils::require_native_mint(&ctx.accounts.mint) + let mint = Mint::load(&ctx.accounts.mint)?; + require!( + !crate::utils::is_wrapped_mint(&mint), + TokenBridgeError::WrappedAsset + ); + + Ok(()) } } diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/mod.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/mod.rs index bb9822f662..46dce4b05f 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/mod.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/mod.rs @@ -12,7 +12,7 @@ use core_bridge_program::{ }; use wormhole_raw_vaas::token_bridge::TokenBridgeMessage; -pub fn validate_posted_token_transfer( +pub fn validate_token_transfer_vaa( vaa_acc_info: &AccountInfo, registered_emitter: &Account>, recipient_token: &AccountInfo, @@ -20,8 +20,7 @@ pub fn validate_posted_token_transfer( ) -> Result<(u16, [u8; 32])> { let vaa_key = vaa_acc_info.key(); let vaa = core_bridge_sdk::VaaAccount::load(vaa_acc_info)?; - let msg = - crate::utils::require_valid_posted_token_bridge_vaa(&vaa_key, &vaa, registered_emitter)?; + let msg = crate::utils::require_valid_token_bridge_vaa(&vaa_key, &vaa, registered_emitter)?; let transfer = if let TokenBridgeMessage::Transfer(inner) = msg { inner diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/native.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/native.rs index d9ff7131f7..f4d1b098f3 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/native.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/native.rs @@ -34,7 +34,7 @@ pub struct CompleteTransferNative<'info> { /// allows registering multiple emitter addresses for the same chain ID. These seeds are not /// checked via Anchor macro, but will be checked in the access control function instead. /// - /// See the `require_valid_token_bridge_posted_vaa` instruction handler for more details. + /// See the `require_valid_token_bridge_vaa` instruction handler for more details. registered_emitter: Account<'info, LegacyAnchorized<0, RegisteredEmitter>>, /// CHECK: Recipient token account. Because we check the mint of the custody token account, we @@ -139,9 +139,13 @@ impl<'info> CompleteTransferNative<'info> { // Make sure the mint authority is not the Token Bridge's. If it is, then this mint // originated from a foreign network. - crate::utils::require_native_mint(&ctx.accounts.mint)?; + let mint = Mint::load(&ctx.accounts.mint)?; + require!( + !crate::utils::is_wrapped_mint(&mint), + TokenBridgeError::WrappedAsset + ); - let (token_chain, token_address) = super::validate_posted_token_transfer( + let (token_chain, token_address) = super::validate_token_transfer_vaa( &ctx.accounts.vaa, &ctx.accounts.registered_emitter, &ctx.accounts.recipient_token, diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/wrapped.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/wrapped.rs index 2eb3a95fbd..dac74b8e1b 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer/wrapped.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer/wrapped.rs @@ -37,7 +37,7 @@ pub struct CompleteTransferWrapped<'info> { /// allows registering multiple emitter addresses for the same chain ID. These seeds are not /// checked via Anchor macro, but will be checked in the access control function instead. /// - /// See the `require_valid_token_bridge_posted_vaa` instruction handler for more details. + /// See the `require_valid_token_bridge_vaa` instruction handler for more details. registered_emitter: Account<'info, LegacyAnchorized<0, RegisteredEmitter>>, /// CHECK: Recipient token account. Because we verify the wrapped mint, we can depend on the @@ -54,8 +54,9 @@ pub struct CompleteTransferWrapped<'info> { /// CHECK: Wrapped mint (i.e. minted by Token Bridge program). /// - /// NOTE: Instead of checking the seeds, we check that the mint authority is the Token Bridge's - /// in access control. + /// NOTE: Because this mint is guaranteed to have a Wrapped Asset account (since this account's + /// pubkey is a part of the Wrapped Asset's PDA address), we do not need to check that this + /// mint is one that the Token Bridge program has mint authority for. #[account(mut)] wrapped_mint: AccountInfo<'info>, @@ -133,13 +134,7 @@ impl<'info> core_bridge_program::legacy::utils::ProcessLegacyInstruction<'info, impl<'info> CompleteTransferWrapped<'info> { fn constraints(ctx: &Context) -> Result<()> { - let mint = crate::zero_copy::Mint::load(&ctx.accounts.wrapped_mint)?; - require!( - mint.mint_authority() == Some(ctx.accounts.mint_authority.key()), - ErrorCode::ConstraintMintMintAuthority - ); - - let (token_chain, token_address) = super::validate_posted_token_transfer( + let (token_chain, token_address) = super::validate_token_transfer_vaa( &ctx.accounts.vaa, &ctx.accounts.registered_emitter, &ctx.accounts.recipient_token, diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/mod.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/mod.rs index b75d43b94c..8fdf785b3c 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/mod.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/mod.rs @@ -12,7 +12,7 @@ use core_bridge_program::{ }; use wormhole_raw_vaas::token_bridge::TokenBridgeMessage; -pub fn validate_posted_token_transfer_with_payload( +pub fn validate_token_transfer_with_payload_vaa( vaa_acc_info: &AccountInfo, registered_emitter: &Account>, redeemer_authority: &Signer, @@ -20,8 +20,7 @@ pub fn validate_posted_token_transfer_with_payload( ) -> Result<(u16, [u8; 32])> { let vaa_key = vaa_acc_info.key(); let vaa = core_bridge_sdk::VaaAccount::load(vaa_acc_info)?; - let msg = - crate::utils::require_valid_posted_token_bridge_vaa(&vaa_key, &vaa, registered_emitter)?; + let msg = crate::utils::require_valid_token_bridge_vaa(&vaa_key, &vaa, registered_emitter)?; let transfer = if let TokenBridgeMessage::TransferWithMessage(inner) = msg { inner @@ -38,10 +37,8 @@ pub fn validate_posted_token_transfer_with_payload( // The encoded transfer recipient can either be the signer of this instruction or a // program whose signer is a PDA using the seeds [b"redeemer"] (and the encoded redeemer - // is the program ID). If the latter, the transfer redeemer can be any PDA that signs + // is the program ID). If the former, the transfer redeemer can be any PDA that signs // for this instruction. - // - // NOTE: Requiring that the transfer redeemer be a signer is a patch. let redeemer = Pubkey::from(transfer.redeemer()); let redeemer_authority = redeemer_authority.key(); if redeemer != redeemer_authority { diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/native.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/native.rs index 21f72dbb00..e2e0c6dbdc 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/native.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/native.rs @@ -34,7 +34,7 @@ pub struct CompleteTransferWithPayloadNative<'info> { /// allows registering multiple emitter addresses for the same chain ID. These seeds are not /// checked via Anchor macro, but will be checked in the access control function instead. /// - /// See the `require_valid_token_bridge_posted_vaa` instruction handler for more details. + /// See the `require_valid_token_bridge_vaa` instruction handler for more details. registered_emitter: Account<'info, LegacyAnchorized<0, RegisteredEmitter>>, /// CHECK: Destination token account. Because we check the mint of the custody token account, we @@ -122,9 +122,13 @@ impl<'info> CompleteTransferWithPayloadNative<'info> { fn constraints(ctx: &Context) -> Result<()> { // Make sure the mint authority is not the Token Bridge's. If it is, then this mint // originated from a foreign network. - crate::utils::require_native_mint(&ctx.accounts.mint)?; + let mint = Mint::load(&ctx.accounts.mint)?; + require!( + !crate::utils::is_wrapped_mint(&mint), + TokenBridgeError::WrappedAsset + ); - let (token_chain, token_address) = super::validate_posted_token_transfer_with_payload( + let (token_chain, token_address) = super::validate_token_transfer_with_payload_vaa( &ctx.accounts.vaa, &ctx.accounts.registered_emitter, &ctx.accounts.redeemer_authority, diff --git a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/wrapped.rs b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/wrapped.rs index 1702e988bf..a62d4b0e92 100644 --- a/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/wrapped.rs +++ b/solana/programs/token-bridge/src/legacy/processor/complete_transfer_with_payload/wrapped.rs @@ -37,7 +37,7 @@ pub struct CompleteTransferWithPayloadWrapped<'info> { /// allows registering multiple emitter addresses for the same chain ID. These seeds are not /// checked via Anchor macro, but will be checked in the access control function instead. /// - /// See the `require_valid_token_bridge_posted_vaa` instruction handler for more details. + /// See the `require_valid_token_bridge_vaa` instruction handler for more details. registered_emitter: Account<'info, LegacyAnchorized<0, RegisteredEmitter>>, /// CHECK: Destination token account. Because we verify the wrapped mint, we can depend on the @@ -53,8 +53,9 @@ pub struct CompleteTransferWithPayloadWrapped<'info> { /// CHECK: Wrapped mint (i.e. minted by Token Bridge program). /// - /// NOTE: Instead of checking the seeds, we check that the mint authority is the Token Bridge's - /// in access control. + /// NOTE: Because this mint is guaranteed to have a Wrapped Asset account (since this account's + /// pubkey is a part of the Wrapped Asset's PDA address), we do not need to check that this + /// mint is one that the Token Bridge program has mint authority for. #[account(mut)] wrapped_mint: AccountInfo<'info>, @@ -125,13 +126,7 @@ impl<'info> core_bridge_program::legacy::utils::ProcessLegacyInstruction<'info, impl<'info> CompleteTransferWithPayloadWrapped<'info> { fn constraints(ctx: &Context) -> Result<()> { - let mint = crate::zero_copy::Mint::load(&ctx.accounts.wrapped_mint)?; - require!( - mint.mint_authority() == Some(ctx.accounts.mint_authority.key()), - ErrorCode::ConstraintMintMintAuthority - ); - - let (token_chain, token_address) = super::validate_posted_token_transfer_with_payload( + let (token_chain, token_address) = super::validate_token_transfer_with_payload_vaa( &ctx.accounts.vaa, &ctx.accounts.registered_emitter, &ctx.accounts.redeemer_authority, diff --git a/solana/programs/token-bridge/src/legacy/processor/create_or_update_wrapped.rs b/solana/programs/token-bridge/src/legacy/processor/create_or_update_wrapped.rs index 664efd5b46..4edaa58f27 100644 --- a/solana/programs/token-bridge/src/legacy/processor/create_or_update_wrapped.rs +++ b/solana/programs/token-bridge/src/legacy/processor/create_or_update_wrapped.rs @@ -28,7 +28,7 @@ pub struct CreateOrUpdateWrapped<'info> { /// allows registering multiple emitter addresses for the same chain ID. These seeds are not /// checked via Anchor macro, but will be checked in the access control function instead. /// - /// See the `require_valid_token_bridge_posted_vaa` instruction handler for more details. + /// See the `require_valid_token_bridge_vaa` instruction handler for more details. registered_emitter: Account<'info, LegacyAnchorized<0, RegisteredEmitter>>, /// CHECK: Posted VAA account, which will be read via zero-copy deserialization in the @@ -173,7 +173,7 @@ impl<'info> CreateOrUpdateWrapped<'info> { // NOTE: Other attestation validation is performed using the try_attestation_* methods, // which were used in the accounts context. - crate::utils::require_valid_posted_token_bridge_vaa( + crate::utils::require_valid_token_bridge_vaa( &vaa.key(), &core_bridge_sdk::VaaAccount::load(vaa).unwrap(), &ctx.accounts.registered_emitter, diff --git a/solana/programs/token-bridge/src/legacy/processor/transfer_tokens/native.rs b/solana/programs/token-bridge/src/legacy/processor/transfer_tokens/native.rs index a2d9a1e774..569dbfd0f8 100644 --- a/solana/programs/token-bridge/src/legacy/processor/transfer_tokens/native.rs +++ b/solana/programs/token-bridge/src/legacy/processor/transfer_tokens/native.rs @@ -153,7 +153,11 @@ impl<'info> TransferTokensNative<'info> { fn constraints(ctx: &Context, args: &TransferTokensArgs) -> Result<()> { // Make sure the mint authority is not the Token Bridge's. If it is, then this mint // originated from a foreign network. - crate::utils::require_native_mint(&ctx.accounts.mint)?; + let mint = Mint::load(&ctx.accounts.mint)?; + require!( + !crate::utils::is_wrapped_mint(&mint), + TokenBridgeError::WrappedAsset + ); // Cannot configure a fee greater than the total transfer amount. require!( diff --git a/solana/programs/token-bridge/src/legacy/processor/transfer_tokens_with_payload/native.rs b/solana/programs/token-bridge/src/legacy/processor/transfer_tokens_with_payload/native.rs index bb470b80b9..23f29477b4 100644 --- a/solana/programs/token-bridge/src/legacy/processor/transfer_tokens_with_payload/native.rs +++ b/solana/programs/token-bridge/src/legacy/processor/transfer_tokens_with_payload/native.rs @@ -1,5 +1,6 @@ use crate::{ constants::{CUSTODY_AUTHORITY_SEED_PREFIX, TRANSFER_AUTHORITY_SEED_PREFIX}, + error::TokenBridgeError, legacy::instruction::TransferTokensWithPayloadArgs, utils::{self, TruncateAmount}, zero_copy::Mint, @@ -160,7 +161,13 @@ impl<'info> TransferTokensWithPayloadNative<'info> { fn constraints(ctx: &Context) -> Result<()> { // Make sure the mint authority is not the Token Bridge's. If it is, then this mint // originated from a foreign network. - crate::utils::require_native_mint(&ctx.accounts.mint) + let mint = Mint::load(&ctx.accounts.mint)?; + require!( + !crate::utils::is_wrapped_mint(&mint), + TokenBridgeError::WrappedAsset + ); + + Ok(()) } } diff --git a/solana/programs/token-bridge/src/processor/governance/mod.rs b/solana/programs/token-bridge/src/processor/governance/mod.rs index 98409bdd7d..25f083b75c 100644 --- a/solana/programs/token-bridge/src/processor/governance/mod.rs +++ b/solana/programs/token-bridge/src/processor/governance/mod.rs @@ -13,7 +13,7 @@ pub fn require_valid_governance_vaa<'ctx>( vaa_key: &'ctx Pubkey, vaa: &'ctx core_bridge_sdk::VaaAccount<'ctx>, ) -> Result> { - crate::utils::require_valid_posted_vaa_key(vaa_key)?; + crate::utils::require_valid_vaa_key(vaa_key)?; let (emitter_address, emitter_chain, _) = vaa.try_emitter_info()?; require!( diff --git a/solana/programs/token-bridge/src/sdk/cpi/complete_transfer.rs b/solana/programs/token-bridge/src/sdk/cpi/complete_transfer.rs index 4e284962df..3280fd0f0a 100644 --- a/solana/programs/token-bridge/src/sdk/cpi/complete_transfer.rs +++ b/solana/programs/token-bridge/src/sdk/cpi/complete_transfer.rs @@ -78,7 +78,7 @@ where { // If whether this mint is wrapped is unspecified, we derive the mint authority, which will cost // some compute units. - let is_wrapped_asset = !crate::utils::is_native_mint(&Mint::load(&accounts.mint())?); + let is_wrapped_asset = crate::utils::is_wrapped_mint(&Mint::load(&accounts.mint())?); complete_transfer_specified(accounts, is_wrapped_asset, signer_seeds) } diff --git a/solana/programs/token-bridge/src/sdk/cpi/transfer_tokens.rs b/solana/programs/token-bridge/src/sdk/cpi/transfer_tokens.rs index e7521425a0..86e962d412 100644 --- a/solana/programs/token-bridge/src/sdk/cpi/transfer_tokens.rs +++ b/solana/programs/token-bridge/src/sdk/cpi/transfer_tokens.rs @@ -125,7 +125,7 @@ where // If whether this mint is wrapped is unspecified, we derive the mint authority, which will cost // some compute units. let is_wrapped_asset = - !crate::utils::is_native_mint(&crate::zero_copy::Mint::load(&accounts.mint())?); + crate::utils::is_wrapped_mint(&crate::zero_copy::Mint::load(&accounts.mint())?); transfer_tokens_specified(accounts, directive, is_wrapped_asset, signer_seeds) } diff --git a/solana/programs/token-bridge/src/utils/token.rs b/solana/programs/token-bridge/src/utils/token.rs index 9fb3c171d5..0de1da9717 100644 --- a/solana/programs/token-bridge/src/utils/token.rs +++ b/solana/programs/token-bridge/src/utils/token.rs @@ -1,30 +1,21 @@ use crate::{ constants::{MAX_DECIMALS, MINT_AUTHORITY_SEED_PREFIX}, - error::TokenBridgeError, zero_copy::Mint, }; use anchor_lang::prelude::*; -use core_bridge_program::sdk::LoadZeroCopy; -/// With an account meant to be a Token Program mint account, make sure it is not a mint that the -/// Token Bridge program controls. -pub fn require_native_mint(acc_info: &AccountInfo) -> Result<()> { - // If there is a mint authority, make sure it is not the Token Bridge's mint authority, which - // controls burn and mint for its wrapped assets. - let mint = Mint::load(acc_info)?; - require!(is_native_mint(&mint), TokenBridgeError::WrappedAsset); - - // Done. - Ok(()) -} - -pub fn is_native_mint(mint: &Mint) -> bool { +/// Basically check whether the mint authority is the Token Bridge's mint authority. +/// +/// NOTE: This method does not guarantee that the mint is a mint created by the Token Bridge program +/// via `create_or_update_wrapped` instruction because someone can transfer mint authority for +/// another mint to the Token Bridge's mint authority. +pub fn is_wrapped_mint(mint: &Mint) -> bool { if let Some(mint_authority) = mint.mint_authority() { let (token_bridge_mint_authority, _) = Pubkey::find_program_address(&[MINT_AUTHORITY_SEED_PREFIX], &crate::ID); - mint_authority != token_bridge_mint_authority + mint_authority == token_bridge_mint_authority } else { - true + false } } diff --git a/solana/programs/token-bridge/src/utils/vaa.rs b/solana/programs/token-bridge/src/utils/vaa.rs index 11ea796d06..07d21d7101 100644 --- a/solana/programs/token-bridge/src/utils/vaa.rs +++ b/solana/programs/token-bridge/src/utils/vaa.rs @@ -15,7 +15,7 @@ const INVALID_POSTED_VAA_KEYS: [&str; 7] = [ ]; /// We disallow certain posted VAA accounts from being used to redeem Token Bridge transfers. -pub fn require_valid_posted_vaa_key(acc_key: &Pubkey) -> Result<()> { +pub fn require_valid_vaa_key(acc_key: &Pubkey) -> Result<()> { // IYKYK. require!( !INVALID_POSTED_VAA_KEYS.contains(&acc_key.to_string().as_str()), @@ -30,12 +30,12 @@ pub fn require_valid_posted_vaa_key(acc_key: &Pubkey) -> Result<()> { /// - Transfer (Payload ID == 1) /// - Attestation (Payload ID == 2) /// - Transfer with Message (Payload ID == 3) -pub fn require_valid_posted_token_bridge_vaa<'ctx>( +pub fn require_valid_token_bridge_vaa<'ctx>( vaa_acc_key: &'ctx Pubkey, vaa: &'ctx core_bridge_sdk::VaaAccount<'ctx>, registered_emitter: &'ctx Account<'_, LegacyAnchorized<0, RegisteredEmitter>>, ) -> Result> { - require_valid_posted_vaa_key(vaa_acc_key)?; + require_valid_vaa_key(vaa_acc_key)?; let (emitter_address, emitter_chain, _) = vaa.try_emitter_info()?; let emitter_key = registered_emitter.key();