Skip to content

Commit

Permalink
Wrap multisig support (#11)
Browse files Browse the repository at this point in the history
Wrap multisig support
  • Loading branch information
grod220 authored Feb 14, 2025
1 parent 57d46c5 commit b30b8a2
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 49 deletions.
32 changes: 16 additions & 16 deletions program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@ pub enum TokenWrapInstruction {
///
/// Accounts expected by this instruction:
///
/// 0. `[w]` Escrow of unwrapped tokens, must be owned by:
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 1. `[w]` Unwrapped token account to wrap
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 2. `[w]` Recipient wrapped token account
/// 3. `[w]` Wrapped mint, must be initialized, address must be:
/// 0. `[w]` Recipient wrapped token account
/// 1. `[w]` Wrapped mint, must be initialized, address must be:
/// `get_wrapped_mint_address(unwrapped_mint_address,
/// wrapped_token_program_id)`
/// 4. `[]` Unwrapped token mint
/// 5. `[]` Wrapped mint authority, address must be:
/// 2. `[]` Wrapped mint authority, address must be:
/// `get_wrapped_mint_authority(wrapped_mint)`
/// 6. `[]` SPL Token program for unwrapped mint
/// 7. `[]` SPL Token program for wrapped mint
/// 3. `[]` SPL Token program for unwrapped mint
/// 4. `[]` SPL Token program for wrapped mint
/// 5. `[w]` Unwrapped token account to wrap
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 6. `[]` Unwrapped token mint
/// 7. `[w]` Escrow of unwrapped tokens, must be owned by:
/// `get_wrapped_mint_authority(wrapped_mint_address)`
/// 8. `[s]` Transfer authority on unwrapped token account. Not required to
/// be a signer if it's a multisig.
/// 9. `..8+M` `[s]` (Optional) M multisig signers on unwrapped token
Expand Down Expand Up @@ -160,27 +160,27 @@ pub fn create_mint(
#[allow(clippy::too_many_arguments)]
pub fn wrap(
program_id: &Pubkey,
unwrapped_escrow_address: &Pubkey,
unwrapped_token_account_address: &Pubkey,
recipient_wrapped_token_account_address: &Pubkey,
wrapped_mint_address: &Pubkey,
unwrapped_mint_address: &Pubkey,
wrapped_mint_authority_address: &Pubkey,
unwrapped_token_program_id: &Pubkey,
wrapped_token_program_id: &Pubkey,
unwrapped_token_account_address: &Pubkey,
unwrapped_mint_address: &Pubkey,
unwrapped_escrow_address: &Pubkey,
transfer_authority_address: &Pubkey,
multisig_signer_pubkeys: &[&Pubkey],
amount: u64,
) -> Instruction {
let mut accounts = vec![
AccountMeta::new(*unwrapped_escrow_address, false),
AccountMeta::new(*unwrapped_token_account_address, false),
AccountMeta::new(*recipient_wrapped_token_account_address, false),
AccountMeta::new(*wrapped_mint_address, false),
AccountMeta::new_readonly(*unwrapped_mint_address, false),
AccountMeta::new_readonly(*wrapped_mint_authority_address, false),
AccountMeta::new_readonly(*unwrapped_token_program_id, false),
AccountMeta::new_readonly(*wrapped_token_program_id, false),
AccountMeta::new(*unwrapped_token_account_address, false),
AccountMeta::new_readonly(*unwrapped_mint_address, false),
AccountMeta::new(*unwrapped_escrow_address, false),
AccountMeta::new_readonly(
*transfer_authority_address,
multisig_signer_pubkeys.is_empty(),
Expand Down
26 changes: 13 additions & 13 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,16 @@ pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64)

let account_info_iter = &mut accounts.iter();

let unwrapped_escrow = next_account_info(account_info_iter)?;
let unwrapped_token_account = next_account_info(account_info_iter)?;
let recipient_wrapped_token_account = next_account_info(account_info_iter)?;
let wrapped_mint = next_account_info(account_info_iter)?;
let unwrapped_mint = next_account_info(account_info_iter)?;
let wrapped_mint_authority = next_account_info(account_info_iter)?;
let unwrapped_token_program = next_account_info(account_info_iter)?;
let wrapped_token_program = next_account_info(account_info_iter)?;
let unwrapped_token_account = next_account_info(account_info_iter)?;
let unwrapped_mint = next_account_info(account_info_iter)?;
let unwrapped_escrow = next_account_info(account_info_iter)?;
let transfer_authority = next_account_info(account_info_iter)?;
let _signer_accounts = account_info_iter.as_slice();
let multisig_signer_accounts = account_info_iter.as_slice();

// Validate accounts

Expand All @@ -207,24 +207,24 @@ pub fn process_wrap(_program_id: &Pubkey, accounts: &[AccountInfo], amount: u64)

let unwrapped_mint_data = unwrapped_mint.try_borrow_data()?;
let unwrapped_mint_state = PodStateWithExtensions::<PodMint>::unpack(&unwrapped_mint_data)?;
invoke_signed(

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

invoke(
&spl_token_2022::instruction::transfer_checked(
unwrapped_token_program.key,
unwrapped_token_account.key,
unwrapped_mint.key,
unwrapped_escrow.key,
transfer_authority.key,
&[],
&multisig_signer_pubkeys,
amount,
unwrapped_mint_state.base.decimals,
)?,
&[
unwrapped_token_account.clone(),
unwrapped_mint.clone(),
unwrapped_escrow.clone(),
transfer_authority.clone(),
],
&[],
&accounts[5..],
)?;

// Mint wrapped tokens to recipient
Expand Down
46 changes: 45 additions & 1 deletion program/tests/helpers/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use {
crate::helpers::create_mint_builder::TokenProgram,
crate::helpers::{
create_mint_builder::{KeyedAccount, TokenProgram},
wrap_builder::TransferAuthority,
},
mollusk_svm::Mollusk,
solana_account::Account,
solana_program_option::COption,
Expand Down Expand Up @@ -89,3 +92,44 @@ pub fn setup_mint(token_program: TokenProgram, rent: &Rent, mint_authority: Pubk
..Default::default()
}
}

pub fn setup_multisig(program: TokenProgram) -> TransferAuthority {
let multisig_key = Pubkey::new_unique();
let signer0_key = Pubkey::new_unique();
let signer1_key = Pubkey::new_unique();
let signer2_key = Pubkey::new_unique();

let mut multisig_account = Account {
lamports: 100_000_000,
owner: program.id(),
data: vec![0; spl_token_2022::state::Multisig::LEN],
..Account::default()
};

let multisig_state = spl_token_2022::state::Multisig {
m: 2,
n: 3,
is_initialized: true,
signers: [
signer0_key,
signer1_key,
signer2_key,
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
],
};
spl_token_2022::state::Multisig::pack(multisig_state, &mut multisig_account.data).unwrap();
TransferAuthority {
keyed_account: KeyedAccount {
key: multisig_key,
account: multisig_account,
},
signers: vec![signer0_key, signer1_key, signer2_key],
}
}
2 changes: 1 addition & 1 deletion program/tests/helpers/create_mint_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct CreateMintResult {
pub wrapped_backpointer: KeyedAccount,
}

#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct KeyedAccount {
pub key: Pubkey,
pub account: Account,
Expand Down
54 changes: 37 additions & 17 deletions program/tests/helpers/wrap_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ use {
spl_token_wrap::{get_wrapped_mint_address, get_wrapped_mint_authority, instruction::wrap},
};

#[derive(Debug, Clone, Default)]
pub struct TransferAuthority {
pub keyed_account: KeyedAccount,
pub signers: Vec<Pubkey>,
}

pub struct WrapBuilder<'a> {
mollusk: Mollusk,
wrap_amount: Option<u64>,
Expand All @@ -31,6 +37,7 @@ pub struct WrapBuilder<'a> {
unwrapped_escrow_account: Option<Account>,
unwrapped_token_program: Option<TokenProgram>,
wrapped_token_program: Option<TokenProgram>,
transfer_authority: Option<TransferAuthority>,
}

impl Default for WrapBuilder<'_> {
Expand All @@ -49,6 +56,7 @@ impl Default for WrapBuilder<'_> {
unwrapped_escrow_account: None,
unwrapped_token_program: None,
wrapped_token_program: None,
transfer_authority: None,
}
}
}
Expand Down Expand Up @@ -104,6 +112,11 @@ impl<'a> WrapBuilder<'a> {
self
}

pub fn transfer_authority(mut self, auth: TransferAuthority) -> Self {
self.transfer_authority = Some(auth);
self
}

pub fn check(mut self, check: Check<'a>) -> Self {
self.checks.push(check);
self
Expand Down Expand Up @@ -178,7 +191,7 @@ impl<'a> WrapBuilder<'a> {

pub fn execute(mut self) -> WrapResult {
let unwrapped_token_account_address = Pubkey::new_unique();
let unwrapped_token_account_authority = Pubkey::new_unique();
let unwrapped_token_account_authority = self.transfer_authority.clone().unwrap_or_default();

let unwrapped_token_program = self
.unwrapped_token_program
Expand All @@ -204,7 +217,7 @@ impl<'a> WrapBuilder<'a> {

let token = spl_token::state::Account {
mint: unwrapped_mint.key,
owner: unwrapped_token_account_authority,
owner: unwrapped_token_account_authority.keyed_account.key,
amount: self.unwrapped_token_starting_amount.unwrap_or(wrap_amount),
delegate: None.into(),
state: spl_token::state::AccountState::Initialized,
Expand Down Expand Up @@ -258,42 +271,49 @@ impl<'a> WrapBuilder<'a> {

let instruction = wrap(
&spl_token_wrap::id(),
&unwrapped_escrow_address,
&unwrapped_token_account_address,
&recipient.key,
&wrapped_mint.key,
&unwrapped_mint.key,
&wrapped_mint_authority,
&unwrapped_token_program.id(),
&wrapped_token_program.id(),
&unwrapped_token_account_authority,
&[],
&unwrapped_token_account_address,
&unwrapped_mint.key,
&unwrapped_escrow_address,
&unwrapped_token_account_authority.keyed_account.key,
&unwrapped_token_account_authority
.signers
.iter()
.collect::<Vec<_>>(),
wrap_amount,
);

let accounts = &[
(
unwrapped_escrow_address,
self.unwrapped_escrow_account
.unwrap_or(unwrapped_escrow_account),
),
(unwrapped_token_account_address, unwrapped_token_account),
let mut accounts = vec![
recipient.pair(),
wrapped_mint.pair(),
unwrapped_mint.pair(),
(wrapped_mint_authority, Account::default()),
unwrapped_token_program.keyed_account(),
wrapped_token_program.keyed_account(),
(unwrapped_token_account_authority, Account::default()),
(unwrapped_token_account_address, unwrapped_token_account),
unwrapped_mint.pair(),
(
unwrapped_escrow_address,
self.unwrapped_escrow_account
.unwrap_or(unwrapped_escrow_account),
),
unwrapped_token_account_authority.keyed_account.pair(),
];

for signer_key in &unwrapped_token_account_authority.signers {
accounts.push((*signer_key, Account::default()));
}

if self.checks.is_empty() {
self.checks.push(Check::success());
}

let result =
self.mollusk
.process_and_validate_instruction(&instruction, accounts, &self.checks);
.process_and_validate_instruction(&instruction, &accounts, &self.checks);

WrapResult {
unwrapped_token: KeyedAccount {
Expand Down
33 changes: 32 additions & 1 deletion program/tests/test_wrap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::helpers::{
common::MINT_SUPPLY,
common::{setup_multisig, MINT_SUPPLY},
create_mint_builder::{CreateMintBuilder, KeyedAccount, TokenProgram},
wrap_builder::{WrapBuilder, WrapResult},
},
Expand Down Expand Up @@ -163,3 +163,34 @@ fn test_successful_spl_token_2022_to_token_2022() {

assert_wrap_result(starting_amount, wrap_amount, &wrap_result);
}

#[test]
fn test_wrap_with_spl_token_multisig() {
let starting_amount = 500_000;
let wrap_amount = 8_000;
let multisig = setup_multisig(TokenProgram::SplToken);

let wrap_result = WrapBuilder::default()
.transfer_authority(multisig)
.recipient_starting_amount(starting_amount)
.wrap_amount(wrap_amount)
.execute();

assert_wrap_result(starting_amount, wrap_amount, &wrap_result);
}

#[test]
fn test_wrap_with_token_2022_multisig() {
let starting_amount = 10_000;
let wrap_amount = 252;
let multisig = setup_multisig(TokenProgram::SplToken2022);

let wrap_result = WrapBuilder::default()
.transfer_authority(multisig)
.unwrapped_token_program(TokenProgram::SplToken2022)
.recipient_starting_amount(starting_amount)
.wrap_amount(wrap_amount)
.execute();

assert_wrap_result(starting_amount, wrap_amount, &wrap_result);
}

0 comments on commit b30b8a2

Please sign in to comment.