Skip to content
Merged
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
32 changes: 18 additions & 14 deletions program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,20 @@ pub enum TokenWrapInstruction {
///
/// Accounts expected by this instruction:
///
/// 0. `[writeable]` Wrapped token account to unwrap
/// 1. `[writeable]` Wrapped mint, address must be:
/// 0. `[writeable]` Escrow of unwrapped tokens, must be owned by:
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 1. `[writeable]` Recipient unwrapped tokens
/// 2. `[]` Wrapped mint authority, address must be:
/// `get_wrapped_mint_authority(wrapped_mint)`
/// 3. `[]` Unwrapped token mint
/// 4. `[]` SPL Token program for wrapped mint
/// 5. `[]` SPL Token program for unwrapped mint
/// 6. `[writeable]` Wrapped token account to unwrap
/// 7. `[writeable]` Wrapped mint, address must be:
/// `get_wrapped_mint_address(unwrapped_mint_address,
/// wrapped_token_program_id)`
/// 2. `[writeable]` Escrow of unwrapped tokens, must be owned by:
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 3. `[writeable]` Recipient unwrapped tokens
/// 4. `[]` Unwrapped token mint
/// 5. `[]` SPL Token program for wrapped mint
/// 6. `[]` SPL Token program for unwrapped mint
/// 7. `[signer]` Transfer authority on wrapped token account
/// 8. `..8+M` `[signer]` (Optional) M multisig signers on wrapped token
/// 8. `[signer]` Transfer authority on wrapped token account
/// 9. `..8+M` `[signer]` (Optional) M multisig signers on wrapped token
Comment on lines +72 to +85
Copy link
Member Author

@grod220 grod220 Feb 14, 2025

Choose a reason for hiding this comment

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

Re-arranged in order so that accounts can be sub-sliced into the transfer_checked account slice

/// account
Unwrap {
/// little-endian `u64` representing the amount to unwrap
Expand Down Expand Up @@ -198,25 +200,27 @@ pub fn wrap(
#[allow(clippy::too_many_arguments)]
pub fn unwrap(
program_id: &Pubkey,
wrapped_token_account_address: &Pubkey,
wrapped_mint_address: &Pubkey,
unwrapped_escrow_address: &Pubkey,
recipient_unwrapped_token_account_address: &Pubkey,
wrapped_mint_authority_address: &Pubkey,
unwrapped_mint_address: &Pubkey,
wrapped_token_program_id: &Pubkey,
unwrapped_token_program_id: &Pubkey,
wrapped_token_account_address: &Pubkey,
wrapped_mint_address: &Pubkey,
transfer_authority_address: &Pubkey,
multisig_signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(*wrapped_token_account_address, false),
AccountMeta::new(*wrapped_mint_address, false),
AccountMeta::new(*unwrapped_escrow_address, false),
AccountMeta::new(*recipient_unwrapped_token_account_address, false),
AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
AccountMeta::new_readonly(*unwrapped_mint_address, false),
AccountMeta::new_readonly(*wrapped_token_program_id, false),
AccountMeta::new_readonly(*unwrapped_token_program_id, false),
AccountMeta::new(*wrapped_token_account_address, false),
AccountMeta::new(*wrapped_mint_address, false),
AccountMeta::new_readonly(
*transfer_authority_address,
multisig_signer_pubkeys.is_empty(),
Expand Down
89 changes: 85 additions & 4 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub fn process_create_mint(
}

/// Processes [`Wrap`](enum.TokenWrapInstruction.html) instruction.
pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult {
pub fn process_wrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
if amount == 0 {
Err(TokenWrapError::ZeroWrapAmount)?
}
Expand Down Expand Up @@ -251,6 +251,87 @@ pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64)
Ok(())
}

/// Processes [`Unwrap`](enum.TokenWrapInstruction.html) instruction.
pub fn process_unwrap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
if amount == 0 {
Err(TokenWrapError::ZeroWrapAmount)?
}

let account_info_iter = &mut accounts.iter();

let unwrapped_escrow = next_account_info(account_info_iter)?;
let recipient_unwrapped_token = next_account_info(account_info_iter)?;
let wrapped_mint_authority = next_account_info(account_info_iter)?;
let unwrapped_mint = next_account_info(account_info_iter)?;
let wrapped_token_program = next_account_info(account_info_iter)?;
let unwrapped_token_program = next_account_info(account_info_iter)?;
let wrapped_token_account = next_account_info(account_info_iter)?;
let wrapped_mint = next_account_info(account_info_iter)?;
let transfer_authority = next_account_info(account_info_iter)?;
let multisig_signer_accounts = account_info_iter.as_slice();

// Validate accounts
Copy link
Contributor

Choose a reason for hiding this comment

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

I was comparing the checks here to the token-upgrade program, which is very similar to this instruction in particular.

I'm not sure if it's actually needed, but that program does a whole bunch of checks on each account, to make sure they're really token accounts and all that.

Here's some owner checks: https://github.com/solana-labs/solana-program-library/blob/63b217336276c1e41163a5ac030d1b5a85741782/token-upgrade/program/src/processor.rs#L96

And here's some account type checks: https://github.com/solana-labs/solana-program-library/blob/63b217336276c1e41163a5ac030d1b5a85741782/token-upgrade/program/src/processor.rs#L126

I could have been overly cautious in that program though. @buffalojoec do you think we need them?

Choose a reason for hiding this comment

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

I would say no, we don't need account state and owner checks, since everything has to go through an SPL-Token instruction (mint_to, burn, transfer_checked) and the respective token program should have those checks. It's better to leave those checks up to the token program anyway, since this program could theoretically be used with alternate token programs.

Verifying that you're unwrapping to the proper token (and wrapping in the previous instruction) is done by the PDA derivations, so I think we're covered.


let expected_wrapped_mint =
get_wrapped_mint_address(unwrapped_mint.key, wrapped_token_program.key);
if expected_wrapped_mint != *wrapped_mint.key {
Err(TokenWrapError::WrappedMintMismatch)?
}

let (expected_authority, bump) = get_wrapped_mint_authority_with_seed(wrapped_mint.key);
if *wrapped_mint_authority.key != expected_authority {
Err(TokenWrapError::MintAuthorityMismatch)?
}

// Burn wrapped tokens

let multisig_signer_pubkeys = multisig_signer_accounts
.iter()
.map(|account| account.key)
.collect::<Vec<_>>();

invoke(
&spl_token_2022::instruction::burn(
wrapped_token_program.key,
wrapped_token_account.key,
wrapped_mint.key,
transfer_authority.key,
&multisig_signer_pubkeys,
amount,
)?,
&accounts[6..],
)?;

// Transfer unwrapped tokens from escrow to recipient

let unwrapped_mint_data = unwrapped_mint.try_borrow_data()?;
let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?;
let bump_seed = [bump];
let signer_seeds = get_wrapped_mint_authority_signer_seeds(wrapped_mint.key, &bump_seed);

invoke_signed(
&spl_token_2022::instruction::transfer_checked(
unwrapped_token_program.key,
unwrapped_escrow.key,
unwrapped_mint.key,
recipient_unwrapped_token.key,
wrapped_mint_authority.key,
&[],
amount,
unwrapped_mint_state.base.decimals,
)?,
&[
unwrapped_escrow.clone(),
unwrapped_mint.clone(),
recipient_unwrapped_token.clone(),
wrapped_mint_authority.clone(),
],
&[&signer_seeds],
)?;

Ok(())
}

/// Instruction processor
pub fn process_instruction(
program_id: &Pubkey,
Expand All @@ -264,11 +345,11 @@ pub fn process_instruction(
}
TokenWrapInstruction::Wrap { amount } => {
msg!("Instruction: Wrap");
process_wrap(program_id, accounts, amount)
process_wrap(accounts, amount)
}
TokenWrapInstruction::Unwrap { .. } => {
TokenWrapInstruction::Unwrap { amount } => {
msg!("Instruction: Unwrap");
unimplemented!();
process_unwrap(accounts, amount)
}
}
}
1 change: 1 addition & 0 deletions program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod common;
pub mod create_mint_builder;
pub mod unwrap_builder;
pub mod wrap_builder;
Loading
Loading