Add `token_program` constraint to token, mint, and associated token accounts (#2460)
This commit is contained in:
parent
b11d63861c
commit
670b4f5005
|
@ -377,6 +377,8 @@ jobs:
|
|||
path: tests/errors
|
||||
- cmd: cd tests/spl/token-proxy && anchor test --skip-lint
|
||||
path: spl/token-proxy
|
||||
- cmd: cd tests/spl/token-wrapper && anchor test --skip-lint
|
||||
path: spl/token-wrapper
|
||||
- cmd: cd tests/multisig && anchor test --skip-lint
|
||||
path: tests/multisig
|
||||
# - cmd: cd tests/lockup && anchor test --skip-lint
|
||||
|
|
|
@ -13,6 +13,7 @@ The minor version will be incremented upon a breaking change and the patch versi
|
|||
### Features
|
||||
|
||||
- spl: Add metadata wrappers `approve_collection_authority`, `bubblegum_set_collection_size`, `burn_edition_nft`, `burn_nft`, `revoke_collection_authority`, `set_token_standard`, `utilize`, `unverify_sized_collection_item`, `unverify_collection` ([#2430](https://github.com/coral-xyz/anchor/pull/2430))
|
||||
- spl: Add `token_program` constraint to `Token`, `Mint`, and `AssociatedToken` accounts in order to override required `token_program` fields and use different token interface implementations in the same instruction ([#2460](https://github.com/coral-xyz/anchor/pull/2460))
|
||||
- cli: Add support for Solidity programs. `anchor init` and `anchor new` take an option `--solidity` which creates solidity code rather than rust. `anchor build` and `anchor test` work accordingly ([#2421](https://github.com/coral-xyz/anchor/pull/2421))
|
||||
|
||||
### Fixes
|
||||
|
|
|
@ -479,6 +479,8 @@ use syn::parse_macro_input;
|
|||
/// <tr>
|
||||
/// <td>
|
||||
/// <code>#[account(token::mint = <target_account>, token::authority = <target_account>)]</code>
|
||||
/// <br><br>
|
||||
/// <code>#[account(token::mint = <target_account>, token::authority = <target_account>, token::token_program = <target_account>)]</code>
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// Can be used as a check or with <code>init</code> to create a token
|
||||
|
@ -546,6 +548,8 @@ use syn::parse_macro_input;
|
|||
/// <tr>
|
||||
/// <td>
|
||||
/// <code>#[account(associated_token::mint = <target_account>, associated_token::authority = <target_account>)]</code>
|
||||
/// <br><br>
|
||||
/// <code>#[account(associated_token::mint = <target_account>, associated_token::authority = <target_account>, associated_token::token_program = <target_account>)]</code>
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// Can be used as a standalone as a check or with <code>init</code> to create an associated token
|
||||
|
@ -580,6 +584,48 @@ use syn::parse_macro_input;
|
|||
/// pub system_program: Program<'info, System>
|
||||
/// </pre>
|
||||
/// </td>
|
||||
/// </tr><tr>
|
||||
/// <td>
|
||||
/// <code>#[account(*::token_program = <target_account>)]</code>
|
||||
/// </td>
|
||||
/// <td>
|
||||
/// The <code>token_program</code> can optionally be overridden.
|
||||
/// <br><br>
|
||||
/// Example:
|
||||
/// <pre>
|
||||
/// use anchor_spl::{mint, token::{TokenAccount, Mint, Token}};
|
||||
/// ...
|
||||
/// #[account(
|
||||
/// mint::token_program = token_a_token_program,
|
||||
/// )]
|
||||
/// pub token_a_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
/// #[account(
|
||||
/// mint::token_program = token_b_token_program,
|
||||
/// )]
|
||||
/// pub token_b_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
/// #[account(
|
||||
/// init,
|
||||
/// payer = payer,
|
||||
/// token::mint = token_a_mint,
|
||||
/// token::authority = payer,
|
||||
/// token::token_program = token_a_token_program,
|
||||
/// )]
|
||||
/// pub token_a_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
/// #[account(
|
||||
/// init,
|
||||
/// payer = payer,
|
||||
/// token::mint = token_b_mint,
|
||||
/// token::authority = payer,
|
||||
/// token::token_program = token_b_token_program,
|
||||
/// )]
|
||||
/// pub token_b_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
/// pub token_a_token_program: Interface<'info, TokenInterface>,
|
||||
/// pub token_b_token_program: Interface<'info, TokenInterface>,
|
||||
/// #[account(mut)]
|
||||
/// pub payer: Signer<'info>,
|
||||
/// pub system_program: Program<'info, System>
|
||||
/// </pre>
|
||||
/// </td>
|
||||
/// </tr>
|
||||
/// <tbody>
|
||||
/// </table>
|
||||
|
|
|
@ -110,6 +110,17 @@ pub enum ErrorCode {
|
|||
/// 2020 - A required account for the constraint is None
|
||||
#[msg("A required account for the constraint is None")]
|
||||
ConstraintAccountIsNone,
|
||||
/// The token token is intentional -> a token program for the token account.
|
||||
///
|
||||
/// 2021 - A token account token program constraint was violated
|
||||
#[msg("A token account token program constraint was violated")]
|
||||
ConstraintTokenTokenProgram,
|
||||
/// 2022 - A mint token program constraint was violated
|
||||
#[msg("A mint token program constraint was violated")]
|
||||
ConstraintMintTokenProgram,
|
||||
/// 2023 - A mint token program constraint was violated
|
||||
#[msg("An associated token account token program constraint was violated")]
|
||||
ConstraintAssociatedTokenTokenProgram,
|
||||
|
||||
// Require
|
||||
/// 2500 - A require expression was violated
|
||||
|
|
|
@ -497,18 +497,26 @@ fn generate_constraint_init_group(
|
|||
|
||||
// Optional check idents
|
||||
let system_program = "e! {system_program};
|
||||
let token_program = "e! {token_program};
|
||||
let associated_token_program = "e! {associated_token_program};
|
||||
let rent = "e! {rent};
|
||||
|
||||
let mut check_scope = OptionalCheckScope::new_with_field(accs, field);
|
||||
match &c.kind {
|
||||
InitKind::Token { owner, mint } => {
|
||||
InitKind::Token {
|
||||
owner,
|
||||
mint,
|
||||
token_program,
|
||||
} => {
|
||||
let token_program = match token_program {
|
||||
Some(t) => t.to_token_stream(),
|
||||
None => quote! {token_program},
|
||||
};
|
||||
|
||||
let owner_optional_check = check_scope.generate_check(owner);
|
||||
let mint_optional_check = check_scope.generate_check(mint);
|
||||
|
||||
let system_program_optional_check = check_scope.generate_check(system_program);
|
||||
let token_program_optional_check = check_scope.generate_check(token_program);
|
||||
let token_program_optional_check = check_scope.generate_check(&token_program);
|
||||
let rent_optional_check = check_scope.generate_check(rent);
|
||||
|
||||
let optional_checks = quote! {
|
||||
|
@ -526,7 +534,7 @@ fn generate_constraint_init_group(
|
|||
let create_account = generate_create_account(
|
||||
field,
|
||||
quote! {#token_account_space},
|
||||
quote! {&token_program.key()},
|
||||
quote! {&#token_program.key()},
|
||||
quote! {#payer},
|
||||
seeds_with_bump,
|
||||
);
|
||||
|
@ -539,14 +547,15 @@ fn generate_constraint_init_group(
|
|||
// Checks that all the required accounts for this operation are present.
|
||||
#optional_checks
|
||||
|
||||
if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
|
||||
let owner_program = AsRef::<AccountInfo>::as_ref(&#field).owner;
|
||||
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
|
||||
#payer_optional_check
|
||||
|
||||
// Create the account with the system program.
|
||||
#create_account
|
||||
|
||||
// Initialize the token account.
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let cpi_program = #token_program.to_account_info();
|
||||
let accounts = ::anchor_spl::token_interface::InitializeAccount3 {
|
||||
account: #field.to_account_info(),
|
||||
mint: #mint.to_account_info(),
|
||||
|
@ -564,17 +573,28 @@ fn generate_constraint_init_group(
|
|||
if pa.owner != #owner.key() {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
|
||||
}
|
||||
if owner_program != &#token_program.key() {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
|
||||
}
|
||||
}
|
||||
pa
|
||||
};
|
||||
}
|
||||
}
|
||||
InitKind::AssociatedToken { owner, mint } => {
|
||||
InitKind::AssociatedToken {
|
||||
owner,
|
||||
mint,
|
||||
token_program,
|
||||
} => {
|
||||
let token_program = match token_program {
|
||||
Some(t) => t.to_token_stream(),
|
||||
None => quote! {token_program},
|
||||
};
|
||||
let owner_optional_check = check_scope.generate_check(owner);
|
||||
let mint_optional_check = check_scope.generate_check(mint);
|
||||
|
||||
let system_program_optional_check = check_scope.generate_check(system_program);
|
||||
let token_program_optional_check = check_scope.generate_check(token_program);
|
||||
let token_program_optional_check = check_scope.generate_check(&token_program);
|
||||
let associated_token_program_optional_check =
|
||||
check_scope.generate_check(associated_token_program);
|
||||
let rent_optional_check = check_scope.generate_check(rent);
|
||||
|
@ -598,7 +618,8 @@ fn generate_constraint_init_group(
|
|||
// Checks that all the required accounts for this operation are present.
|
||||
#optional_checks
|
||||
|
||||
if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
|
||||
let owner_program = AsRef::<AccountInfo>::as_ref(&#field).owner;
|
||||
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
|
||||
#payer_optional_check
|
||||
|
||||
let cpi_program = associated_token_program.to_account_info();
|
||||
|
@ -608,7 +629,7 @@ fn generate_constraint_init_group(
|
|||
authority: #owner.to_account_info(),
|
||||
mint: #mint.to_account_info(),
|
||||
system_program: system_program.to_account_info(),
|
||||
token_program: token_program.to_account_info(),
|
||||
token_program: #token_program.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, cpi_accounts);
|
||||
::anchor_spl::associated_token::create(cpi_ctx)?;
|
||||
|
@ -621,6 +642,9 @@ fn generate_constraint_init_group(
|
|||
if pa.owner != #owner.key() {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
|
||||
}
|
||||
if owner_program != &#token_program.key() {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociatedTokenTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
|
||||
}
|
||||
|
||||
if pa.key() != ::anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str));
|
||||
|
@ -634,7 +658,12 @@ fn generate_constraint_init_group(
|
|||
owner,
|
||||
decimals,
|
||||
freeze_authority,
|
||||
token_program,
|
||||
} => {
|
||||
let token_program = match token_program {
|
||||
Some(t) => t.to_token_stream(),
|
||||
None => quote! {token_program},
|
||||
};
|
||||
let owner_optional_check = check_scope.generate_check(owner);
|
||||
let freeze_authority_optional_check = match freeze_authority {
|
||||
Some(fa) => check_scope.generate_check(fa),
|
||||
|
@ -642,7 +671,7 @@ fn generate_constraint_init_group(
|
|||
};
|
||||
|
||||
let system_program_optional_check = check_scope.generate_check(system_program);
|
||||
let token_program_optional_check = check_scope.generate_check(token_program);
|
||||
let token_program_optional_check = check_scope.generate_check(&token_program);
|
||||
let rent_optional_check = check_scope.generate_check(rent);
|
||||
|
||||
let optional_checks = quote! {
|
||||
|
@ -658,7 +687,7 @@ fn generate_constraint_init_group(
|
|||
let create_account = generate_create_account(
|
||||
field,
|
||||
quote! {::anchor_spl::token::Mint::LEN},
|
||||
quote! {&token_program.key()},
|
||||
quote! {&#token_program.key()},
|
||||
quote! {#payer},
|
||||
seeds_with_bump,
|
||||
);
|
||||
|
@ -676,7 +705,8 @@ fn generate_constraint_init_group(
|
|||
// Checks that all the required accounts for this operation are present.
|
||||
#optional_checks
|
||||
|
||||
if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
|
||||
let owner_program = AsRef::<AccountInfo>::as_ref(&#field).owner;
|
||||
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
|
||||
// Define payer variable.
|
||||
#payer_optional_check
|
||||
|
||||
|
@ -684,7 +714,7 @@ fn generate_constraint_init_group(
|
|||
#create_account
|
||||
|
||||
// Initialize the mint account.
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let cpi_program = #token_program.to_account_info();
|
||||
let accounts = ::anchor_spl::token_interface::InitializeMint2 {
|
||||
mint: #field.to_account_info(),
|
||||
};
|
||||
|
@ -705,6 +735,9 @@ fn generate_constraint_init_group(
|
|||
if pa.decimals != #decimals {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str).with_values((pa.decimals, #decimals)));
|
||||
}
|
||||
if owner_program != &#token_program.key() {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
|
||||
}
|
||||
}
|
||||
pa
|
||||
};
|
||||
|
@ -888,10 +921,21 @@ fn generate_constraint_associated_token(
|
|||
#wallet_address_optional_check
|
||||
#spl_token_mint_address_optional_check
|
||||
};
|
||||
let token_program_check = match &c.token_program {
|
||||
Some(token_program) => {
|
||||
let token_program_optional_check = optional_check_scope.generate_check(token_program);
|
||||
quote! {
|
||||
#token_program_optional_check
|
||||
if #name.to_account_info().owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintAssociatedTokenTokenProgram.into()); }
|
||||
}
|
||||
}
|
||||
None => quote! {},
|
||||
};
|
||||
|
||||
quote! {
|
||||
{
|
||||
#optional_checks
|
||||
#token_program_check
|
||||
|
||||
let my_owner = #name.owner;
|
||||
let wallet_address = #wallet_address.key();
|
||||
|
@ -934,10 +978,21 @@ fn generate_constraint_token_account(
|
|||
}
|
||||
None => quote! {},
|
||||
};
|
||||
let token_program_check = match &c.token_program {
|
||||
Some(token_program) => {
|
||||
let token_program_optional_check = optional_check_scope.generate_check(token_program);
|
||||
quote! {
|
||||
#token_program_optional_check
|
||||
if #name.to_account_info().owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenTokenProgram.into()); }
|
||||
}
|
||||
}
|
||||
None => quote! {},
|
||||
};
|
||||
quote! {
|
||||
{
|
||||
#authority_check
|
||||
#mint_check
|
||||
#token_program_check
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -983,11 +1038,22 @@ fn generate_constraint_mint(
|
|||
}
|
||||
None => quote! {},
|
||||
};
|
||||
let token_program_check = match &c.token_program {
|
||||
Some(token_program) => {
|
||||
let token_program_optional_check = optional_check_scope.generate_check(token_program);
|
||||
quote! {
|
||||
#token_program_optional_check
|
||||
if #name.to_account_info().owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintMintTokenProgram.into()); }
|
||||
}
|
||||
}
|
||||
None => quote! {},
|
||||
};
|
||||
quote! {
|
||||
{
|
||||
#decimal_check
|
||||
#mint_authority_check
|
||||
#freeze_authority_check
|
||||
#token_program_check
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -662,11 +662,14 @@ pub enum ConstraintToken {
|
|||
Address(Context<ConstraintAddress>),
|
||||
TokenMint(Context<ConstraintTokenMint>),
|
||||
TokenAuthority(Context<ConstraintTokenAuthority>),
|
||||
TokenTokenProgram(Context<ConstraintTokenProgram>),
|
||||
AssociatedTokenMint(Context<ConstraintTokenMint>),
|
||||
AssociatedTokenAuthority(Context<ConstraintTokenAuthority>),
|
||||
AssociatedTokenTokenProgram(Context<ConstraintTokenProgram>),
|
||||
MintAuthority(Context<ConstraintMintAuthority>),
|
||||
MintFreezeAuthority(Context<ConstraintMintFreezeAuthority>),
|
||||
MintDecimals(Context<ConstraintMintDecimals>),
|
||||
MintTokenProgram(Context<ConstraintTokenProgram>),
|
||||
Bump(Context<ConstraintTokenBump>),
|
||||
ProgramSeed(Context<ConstraintProgramSeed>),
|
||||
Realloc(Context<ConstraintRealloc>),
|
||||
|
@ -802,15 +805,18 @@ pub enum InitKind {
|
|||
Token {
|
||||
owner: Expr,
|
||||
mint: Expr,
|
||||
token_program: Option<Expr>,
|
||||
},
|
||||
AssociatedToken {
|
||||
owner: Expr,
|
||||
mint: Expr,
|
||||
token_program: Option<Expr>,
|
||||
},
|
||||
Mint {
|
||||
owner: Expr,
|
||||
freeze_authority: Option<Expr>,
|
||||
decimals: Expr,
|
||||
token_program: Option<Expr>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -829,6 +835,11 @@ pub struct ConstraintTokenAuthority {
|
|||
pub auth: Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstraintTokenProgram {
|
||||
token_program: Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstraintMintAuthority {
|
||||
pub mint_auth: Expr,
|
||||
|
@ -858,12 +869,14 @@ pub struct ConstraintProgramSeed {
|
|||
pub struct ConstraintAssociatedToken {
|
||||
pub wallet: Expr,
|
||||
pub mint: Expr,
|
||||
pub token_program: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstraintTokenAccountGroup {
|
||||
pub mint: Option<Expr>,
|
||||
pub authority: Option<Expr>,
|
||||
pub token_program: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -871,6 +884,7 @@ pub struct ConstraintTokenMintGroup {
|
|||
pub decimals: Option<Expr>,
|
||||
pub mint_authority: Option<Expr>,
|
||||
pub freeze_authority: Option<Expr>,
|
||||
pub token_program: Option<Expr>,
|
||||
}
|
||||
|
||||
// Syntaxt context object for preserving metadata about the inner item.
|
||||
|
|
|
@ -84,6 +84,12 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
|||
decimals: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
"token_program" => ConstraintToken::MintTokenProgram(Context::new(
|
||||
span,
|
||||
ConstraintTokenProgram {
|
||||
token_program: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +117,12 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
|||
auth: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
"token_program" => ConstraintToken::TokenTokenProgram(Context::new(
|
||||
span,
|
||||
ConstraintTokenProgram {
|
||||
token_program: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +150,12 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
|||
auth: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
"token_program" => ConstraintToken::AssociatedTokenTokenProgram(Context::new(
|
||||
span,
|
||||
ConstraintTokenProgram {
|
||||
token_program: stream.parse()?,
|
||||
},
|
||||
)),
|
||||
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
|
||||
}
|
||||
}
|
||||
|
@ -332,11 +350,14 @@ pub struct ConstraintGroupBuilder<'ty> {
|
|||
pub address: Option<Context<ConstraintAddress>>,
|
||||
pub token_mint: Option<Context<ConstraintTokenMint>>,
|
||||
pub token_authority: Option<Context<ConstraintTokenAuthority>>,
|
||||
pub token_token_program: Option<Context<ConstraintTokenProgram>>,
|
||||
pub associated_token_mint: Option<Context<ConstraintTokenMint>>,
|
||||
pub associated_token_authority: Option<Context<ConstraintTokenAuthority>>,
|
||||
pub associated_token_token_program: Option<Context<ConstraintTokenProgram>>,
|
||||
pub mint_authority: Option<Context<ConstraintMintAuthority>>,
|
||||
pub mint_freeze_authority: Option<Context<ConstraintMintFreezeAuthority>>,
|
||||
pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
|
||||
pub mint_token_program: Option<Context<ConstraintTokenProgram>>,
|
||||
pub bump: Option<Context<ConstraintTokenBump>>,
|
||||
pub program_seed: Option<Context<ConstraintProgramSeed>>,
|
||||
pub realloc: Option<Context<ConstraintRealloc>>,
|
||||
|
@ -364,11 +385,14 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
address: None,
|
||||
token_mint: None,
|
||||
token_authority: None,
|
||||
token_token_program: None,
|
||||
associated_token_mint: None,
|
||||
associated_token_authority: None,
|
||||
associated_token_token_program: None,
|
||||
mint_authority: None,
|
||||
mint_freeze_authority: None,
|
||||
mint_decimals: None,
|
||||
mint_token_program: None,
|
||||
bump: None,
|
||||
program_seed: None,
|
||||
realloc: None,
|
||||
|
@ -563,11 +587,14 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
address,
|
||||
token_mint,
|
||||
token_authority,
|
||||
token_token_program,
|
||||
associated_token_mint,
|
||||
associated_token_authority,
|
||||
associated_token_token_program,
|
||||
mint_authority,
|
||||
mint_freeze_authority,
|
||||
mint_decimals,
|
||||
mint_token_program,
|
||||
bump,
|
||||
program_seed,
|
||||
realloc,
|
||||
|
@ -600,20 +627,33 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
.expect("bump must be provided with seeds"),
|
||||
program_seed: into_inner!(program_seed).map(|id| id.program_seed),
|
||||
});
|
||||
let associated_token = match (associated_token_mint, associated_token_authority) {
|
||||
(Some(mint), Some(auth)) => Some(ConstraintAssociatedToken {
|
||||
let associated_token = match (
|
||||
associated_token_mint,
|
||||
associated_token_authority,
|
||||
&associated_token_token_program,
|
||||
) {
|
||||
(Some(mint), Some(auth), _) => Some(ConstraintAssociatedToken {
|
||||
wallet: auth.into_inner().auth,
|
||||
mint: mint.into_inner().mint,
|
||||
token_program: associated_token_token_program
|
||||
.as_ref()
|
||||
.map(|a| a.clone().into_inner().token_program),
|
||||
}),
|
||||
(Some(mint), None) => return Err(ParseError::new(
|
||||
(Some(mint), None, _) => return Err(ParseError::new(
|
||||
mint.span(),
|
||||
"authority must be provided to specify an associated token program derived address",
|
||||
)),
|
||||
(None, Some(auth)) => {
|
||||
(None, Some(auth), _) => {
|
||||
return Err(ParseError::new(
|
||||
auth.span(),
|
||||
"mint must be provided to specify an associated token program derived address",
|
||||
))
|
||||
},
|
||||
(None, None, Some(token_program)) => {
|
||||
return Err(ParseError::new(
|
||||
token_program.span(),
|
||||
"mint and authority must be provided to specify an associated token program derived address",
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
@ -626,18 +666,26 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
}
|
||||
}
|
||||
|
||||
let token_account = match (&token_mint, &token_authority) {
|
||||
(None, None) => None,
|
||||
let token_account = match (&token_mint, &token_authority, &token_token_program) {
|
||||
(None, None, None) => None,
|
||||
_ => Some(ConstraintTokenAccountGroup {
|
||||
mint: token_mint.as_ref().map(|a| a.clone().into_inner().mint),
|
||||
authority: token_authority
|
||||
.as_ref()
|
||||
.map(|a| a.clone().into_inner().auth),
|
||||
token_program: token_token_program
|
||||
.as_ref()
|
||||
.map(|a| a.clone().into_inner().token_program),
|
||||
}),
|
||||
};
|
||||
|
||||
let mint = match (&mint_decimals, &mint_authority, &mint_freeze_authority) {
|
||||
(None, None, None) => None,
|
||||
let mint = match (
|
||||
&mint_decimals,
|
||||
&mint_authority,
|
||||
&mint_freeze_authority,
|
||||
&mint_token_program,
|
||||
) {
|
||||
(None, None, None, None) => None,
|
||||
_ => Some(ConstraintTokenMintGroup {
|
||||
decimals: mint_decimals
|
||||
.as_ref()
|
||||
|
@ -648,6 +696,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
freeze_authority: mint_freeze_authority
|
||||
.as_ref()
|
||||
.map(|a| a.clone().into_inner().mint_freeze_auth),
|
||||
token_program: mint_token_program
|
||||
.as_ref()
|
||||
.map(|a| a.clone().into_inner().token_program),
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -667,11 +718,13 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
"authority must be provided to initialize a token program derived address"
|
||||
)),
|
||||
},
|
||||
token_program: token_token_program.map(|tp| tp.into_inner().token_program),
|
||||
}
|
||||
} else if let Some(at) = &associated_token {
|
||||
InitKind::AssociatedToken {
|
||||
mint: at.mint.clone(),
|
||||
owner: at.wallet.clone()
|
||||
owner: at.wallet.clone(),
|
||||
token_program: associated_token_token_program.map(|tp| tp.into_inner().token_program),
|
||||
}
|
||||
} else if let Some(d) = &mint_decimals {
|
||||
InitKind::Mint {
|
||||
|
@ -683,7 +736,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
"authority must be provided to initialize a mint program derived address"
|
||||
))
|
||||
},
|
||||
freeze_authority: mint_freeze_authority.map(|fa| fa.into_inner().mint_freeze_auth)
|
||||
freeze_authority: mint_freeze_authority.map(|fa| fa.into_inner().mint_freeze_auth),
|
||||
token_program: mint_token_program.map(|tp| tp.into_inner().token_program),
|
||||
}
|
||||
} else {
|
||||
InitKind::Program {
|
||||
|
@ -731,11 +785,16 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
ConstraintToken::Address(c) => self.add_address(c),
|
||||
ConstraintToken::TokenAuthority(c) => self.add_token_authority(c),
|
||||
ConstraintToken::TokenMint(c) => self.add_token_mint(c),
|
||||
ConstraintToken::TokenTokenProgram(c) => self.add_token_token_program(c),
|
||||
ConstraintToken::AssociatedTokenAuthority(c) => self.add_associated_token_authority(c),
|
||||
ConstraintToken::AssociatedTokenMint(c) => self.add_associated_token_mint(c),
|
||||
ConstraintToken::AssociatedTokenTokenProgram(c) => {
|
||||
self.add_associated_token_token_program(c)
|
||||
}
|
||||
ConstraintToken::MintAuthority(c) => self.add_mint_authority(c),
|
||||
ConstraintToken::MintFreezeAuthority(c) => self.add_mint_freeze_authority(c),
|
||||
ConstraintToken::MintDecimals(c) => self.add_mint_decimals(c),
|
||||
ConstraintToken::MintTokenProgram(c) => self.add_mint_token_program(c),
|
||||
ConstraintToken::Bump(c) => self.add_bump(c),
|
||||
ConstraintToken::ProgramSeed(c) => self.add_program_seed(c),
|
||||
ConstraintToken::Realloc(c) => self.add_realloc(c),
|
||||
|
@ -763,6 +822,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
"init must be provided before token authority",
|
||||
));
|
||||
}
|
||||
if self.token_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"init must be provided before token account token program",
|
||||
));
|
||||
}
|
||||
if self.mint_authority.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
|
@ -781,6 +846,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
"init must be provided before mint decimals",
|
||||
));
|
||||
}
|
||||
if self.mint_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"init must be provided before mint token program",
|
||||
));
|
||||
}
|
||||
if self.associated_token_mint.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
|
@ -793,6 +864,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
"init must be provided before associated token authority",
|
||||
));
|
||||
}
|
||||
if self.associated_token_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"init must be provided before associated token account token program",
|
||||
));
|
||||
}
|
||||
self.init.replace(c);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -988,6 +1065,31 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_token_token_program(&mut self, c: Context<ConstraintTokenProgram>) -> ParseResult<()> {
|
||||
if self.token_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"token token_program already provided",
|
||||
));
|
||||
}
|
||||
self.token_token_program.replace(c);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_associated_token_token_program(
|
||||
&mut self,
|
||||
c: Context<ConstraintTokenProgram>,
|
||||
) -> ParseResult<()> {
|
||||
if self.associated_token_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"associated token token_program already provided",
|
||||
));
|
||||
}
|
||||
self.associated_token_token_program.replace(c);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_mint_authority(&mut self, c: Context<ConstraintMintAuthority>) -> ParseResult<()> {
|
||||
if self.mint_authority.is_some() {
|
||||
return Err(ParseError::new(c.span(), "mint authority already provided"));
|
||||
|
@ -1018,6 +1120,17 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn add_mint_token_program(&mut self, c: Context<ConstraintTokenProgram>) -> ParseResult<()> {
|
||||
if self.mint_token_program.is_some() {
|
||||
return Err(ParseError::new(
|
||||
c.span(),
|
||||
"mint token_program already provided",
|
||||
));
|
||||
}
|
||||
self.mint_token_program.replace(c);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_mut(&mut self, c: Context<ConstraintMut>) -> ParseResult<()> {
|
||||
if self.mutable.is_some() {
|
||||
return Err(ParseError::new(c.span(), "mut already provided"));
|
||||
|
|
|
@ -92,14 +92,23 @@ fn constraints_cross_checks(fields: &[AccountField]) -> ParseResult<()> {
|
|||
// init token/a_token/mint needs token program.
|
||||
match kind {
|
||||
InitKind::Program { .. } | InitKind::Interface { .. } => (),
|
||||
InitKind::Token { .. } | InitKind::AssociatedToken { .. } | InitKind::Mint { .. } => {
|
||||
if !fields
|
||||
.iter()
|
||||
.any(|f| f.ident() == "token_program" && !(required_init && f.is_optional()))
|
||||
{
|
||||
InitKind::Token { token_program, .. }
|
||||
| InitKind::AssociatedToken { token_program, .. }
|
||||
| InitKind::Mint { token_program, .. } => {
|
||||
// is the token_program constraint specified?
|
||||
let token_program_field = if let Some(token_program_id) = token_program {
|
||||
// if so, is it present in the struct?
|
||||
token_program_id.to_token_stream().to_string()
|
||||
} else {
|
||||
// if not, look for the token_program field
|
||||
"token_program".to_string()
|
||||
};
|
||||
if !fields.iter().any(|f| {
|
||||
f.ident() == &token_program_field && !(required_init && f.is_optional())
|
||||
}) {
|
||||
return Err(ParseError::new(
|
||||
init_fields[0].ident.span(),
|
||||
message("init", "token_program", required_init),
|
||||
message("init", &token_program_field, required_init),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,24 @@ pub struct TestInitAssociatedToken<'info> {
|
|||
pub associated_token_program: Option<Program<'info, AssociatedToken>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenWithTokenProgram<'info> {
|
||||
#[account(init,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = payer,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: Option<AccountInfo<'info>>,
|
||||
pub associated_token_program: Option<Program<'info, AssociatedToken>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestValidateAssociatedToken<'info> {
|
||||
#[account(
|
||||
|
@ -233,6 +251,23 @@ pub struct TestInitMint<'info> {
|
|||
pub token_program: Option<Program<'info, Token>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitMintWithTokenProgram<'info> {
|
||||
#[account(init,
|
||||
payer = payer,
|
||||
mint::decimals = 6,
|
||||
mint::authority = payer,
|
||||
mint::freeze_authority = payer,
|
||||
mint::token_program = mint_token_program,
|
||||
)]
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitToken<'info> {
|
||||
#[account(init, token::mint = mint, token::authority = payer, payer = payer, )]
|
||||
|
@ -244,6 +279,23 @@ pub struct TestInitToken<'info> {
|
|||
pub token_program: Option<Program<'info, Token>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenWithTokenProgram<'info> {
|
||||
#[account(init,
|
||||
payer = payer,
|
||||
token::mint = mint,
|
||||
token::authority = payer,
|
||||
token::token_program = token_token_program,
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub token_token_program: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestFetchAll<'info> {
|
||||
#[account(init, payer = authority, space = DataWithFilter::LEN + 8)]
|
||||
|
@ -326,6 +378,27 @@ pub struct TestInitMintIfNeeded<'info> {
|
|||
pub freeze_authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitMintIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
mint::decimals = 6,
|
||||
mint::authority = mint_authority,
|
||||
mint::freeze_authority = freeze_authority,
|
||||
mint::token_program = mint_token_program,
|
||||
)]
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: Option<AccountInfo<'info>>,
|
||||
/// CHECK: ignore
|
||||
pub mint_authority: Option<AccountInfo<'info>>,
|
||||
/// CHECK: ignore
|
||||
pub freeze_authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenIfNeeded<'info> {
|
||||
#[account(init_if_needed, token::mint = mint, token::authority = authority, payer = payer, )]
|
||||
|
@ -339,10 +412,28 @@ pub struct TestInitTokenIfNeeded<'info> {
|
|||
pub authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
token::mint = mint,
|
||||
token::authority = authority,
|
||||
token::token_program = token_token_program
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub token_token_program: Option<AccountInfo<'info>>,
|
||||
/// CHECK:
|
||||
pub authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenIfNeeded<'info> {
|
||||
#[account(
|
||||
init_if_needed,
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority
|
||||
|
@ -358,6 +449,26 @@ pub struct TestInitAssociatedTokenIfNeeded<'info> {
|
|||
pub authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
#[account(mut)]
|
||||
pub payer: Option<Signer<'info>>,
|
||||
pub system_program: Option<Program<'info, System>>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: Option<AccountInfo<'info>>,
|
||||
pub associated_token_program: Option<Program<'info, AssociatedToken>>,
|
||||
/// CHECK: ignore
|
||||
pub authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestMultidimensionalArray<'info> {
|
||||
#[account(zero)]
|
||||
|
@ -492,6 +603,16 @@ pub struct TestOnlyAuthorityConstraint<'info> {
|
|||
pub mint: Option<Account<'info, Mint>>,
|
||||
pub payer: Option<Signer<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestOnlyTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
token::token_program = token_token_program
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub token_token_program: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestOnlyMintConstraint<'info> {
|
||||
#[account(
|
||||
|
@ -554,6 +675,16 @@ pub struct TestMintMissMintAuthConstraint<'info> {
|
|||
pub freeze_authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestMintOnlyTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
mint::token_program = mint_token_program,
|
||||
)]
|
||||
pub mint: Option<Account<'info, Mint>>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestAssociatedToken<'info> {
|
||||
#[account(
|
||||
|
@ -564,3 +695,18 @@ pub struct TestAssociatedToken<'info> {
|
|||
pub mint: Option<Account<'info, Mint>>,
|
||||
pub authority: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestAssociatedTokenWithTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Option<Account<'info, TokenAccount>>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
/// CHECK: ignore
|
||||
pub authority: AccountInfo<'info>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: Option<AccountInfo<'info>>,
|
||||
}
|
||||
|
|
|
@ -171,6 +171,10 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_mint_with_token_program(_ctx: Context<TestInitMintWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token(ctx: Context<TestInitToken>) -> Result<()> {
|
||||
assert!(
|
||||
ctx.accounts.token.as_ref().unwrap().mint == ctx.accounts.mint.as_ref().unwrap().key()
|
||||
|
@ -178,6 +182,11 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_with_token_program(_ctx: Context<TestInitTokenWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn test_composite_payer(ctx: Context<TestCompositePayer>) -> Result<()> {
|
||||
ctx.accounts.composite.data.as_mut().unwrap().data = 1;
|
||||
ctx.accounts.data.as_mut().unwrap().udata = 2;
|
||||
|
@ -192,6 +201,10 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_with_token_program(ctx: Context<TestInitAssociatedTokenWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_validate_associated_token(
|
||||
_ctx: Context<TestValidateAssociatedToken>,
|
||||
) -> Result<()> {
|
||||
|
@ -238,16 +251,32 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_mint_if_needed_with_token_program(
|
||||
_ctx: Context<TestInitMintIfNeededWithTokenProgram>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_if_needed(_ctx: Context<TestInitTokenIfNeeded>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_if_needed_with_token_program(_ctx: Context<TestInitTokenIfNeededWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_if_needed(
|
||||
_ctx: Context<TestInitAssociatedTokenIfNeeded>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_if_needed_with_token_program(
|
||||
_ctx: Context<TestInitAssociatedTokenIfNeededWithTokenProgram>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_with_space(_ctx: Context<InitWithSpace>, data: u16) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -328,6 +357,10 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_only_token_program_constraint(_ctx: Context<TestOnlyTokenProgramConstraint>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_mint_constraint(_ctx: Context<TestMintConstraint>, _decimals: u8) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -358,7 +391,17 @@ pub mod misc_optional {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_mint_only_token_program_constraint(
|
||||
_ctx: Context<TestMintOnlyTokenProgramConstraint>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_associated_constraint(_ctx: Context<TestAssociatedToken>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_associated_token_with_token_program_constraint(_ctx: Context<TestAssociatedTokenWithTokenProgramConstraint>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ pub struct TestTokenSeedsInit<'info> {
|
|||
pub struct TestInitAssociatedToken<'info> {
|
||||
#[account(
|
||||
init,
|
||||
associated_token::mint = mint,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = payer,
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
|
@ -47,6 +47,25 @@ pub struct TestInitAssociatedToken<'info> {
|
|||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenWithTokenProgram<'info> {
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = payer,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: AccountInfo<'info>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestValidateAssociatedToken<'info> {
|
||||
#[account(
|
||||
|
@ -230,6 +249,23 @@ pub struct TestInitMint<'info> {
|
|||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitMintWithTokenProgram<'info> {
|
||||
#[account(init,
|
||||
payer = payer,
|
||||
mint::decimals = 6,
|
||||
mint::authority = payer,
|
||||
mint::freeze_authority = payer,
|
||||
mint::token_program = mint_token_program,
|
||||
)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitToken<'info> {
|
||||
#[account(init, token::mint = mint, token::authority = payer, payer = payer, )]
|
||||
|
@ -241,6 +277,23 @@ pub struct TestInitToken<'info> {
|
|||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenWithTokenProgram<'info> {
|
||||
#[account(init,
|
||||
payer = payer,
|
||||
token::mint = mint,
|
||||
token::authority = payer,
|
||||
token::token_program = token_token_program,
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub token_token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestCompositePayer<'info> {
|
||||
pub composite: TestInit<'info>,
|
||||
|
@ -331,6 +384,27 @@ pub struct TestInitMintIfNeeded<'info> {
|
|||
pub freeze_authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitMintIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
mint::decimals = 6,
|
||||
mint::authority = mint_authority,
|
||||
mint::token_program = mint_token_program,
|
||||
mint::freeze_authority = freeze_authority,
|
||||
)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: AccountInfo<'info>,
|
||||
/// CHECK: ignore
|
||||
pub mint_authority: AccountInfo<'info>,
|
||||
/// CHECK: ignore
|
||||
pub freeze_authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenIfNeeded<'info> {
|
||||
#[account(init_if_needed, token::mint = mint, token::authority = authority, payer = payer, )]
|
||||
|
@ -344,10 +418,28 @@ pub struct TestInitTokenIfNeeded<'info> {
|
|||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitTokenIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
token::mint = mint,
|
||||
token::authority = authority,
|
||||
token::token_program = token_token_program
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub token_token_program: AccountInfo<'info>,
|
||||
/// CHECK:
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenIfNeeded<'info> {
|
||||
#[account(
|
||||
init_if_needed,
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority
|
||||
|
@ -363,6 +455,26 @@ pub struct TestInitAssociatedTokenIfNeeded<'info> {
|
|||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestInitAssociatedTokenIfNeededWithTokenProgram<'info> {
|
||||
#[account(init_if_needed,
|
||||
payer = payer,
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
pub system_program: Program<'info, System>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: AccountInfo<'info>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
/// CHECK: ignore
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestMultidimensionalArray<'info> {
|
||||
#[account(zero)]
|
||||
|
@ -489,6 +601,7 @@ pub struct TestAuthorityConstraint<'info> {
|
|||
/// CHECK: ignore
|
||||
pub fake_authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestOnlyAuthorityConstraint<'info> {
|
||||
#[account(
|
||||
|
@ -498,6 +611,16 @@ pub struct TestOnlyAuthorityConstraint<'info> {
|
|||
pub mint: Account<'info, Mint>,
|
||||
pub payer: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestOnlyTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
token::token_program = token_token_program
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub token_token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestOnlyMintConstraint<'info> {
|
||||
#[account(
|
||||
|
@ -566,6 +689,16 @@ pub struct TestMintMissMintAuthConstraint<'info> {
|
|||
pub freeze_authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestMintOnlyTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
mint::token_program = mint_token_program,
|
||||
)]
|
||||
pub mint: Account<'info, Mint>,
|
||||
/// CHECK: ignore
|
||||
pub mint_token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestAssociatedToken<'info> {
|
||||
#[account(
|
||||
|
@ -577,3 +710,18 @@ pub struct TestAssociatedToken<'info> {
|
|||
/// CHECK: ignore
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TestAssociatedTokenWithTokenProgramConstraint<'info> {
|
||||
#[account(
|
||||
associated_token::mint = mint,
|
||||
associated_token::authority = authority,
|
||||
associated_token::token_program = associated_token_token_program,
|
||||
)]
|
||||
pub token: Account<'info, TokenAccount>,
|
||||
pub mint: Account<'info, Mint>,
|
||||
/// CHECK: ignore
|
||||
pub authority: AccountInfo<'info>,
|
||||
/// CHECK: ignore
|
||||
pub associated_token_token_program: AccountInfo<'info>,
|
||||
}
|
||||
|
|
|
@ -175,11 +175,19 @@ pub mod misc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_mint_with_token_program(_ctx: Context<TestInitMintWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token(ctx: Context<TestInitToken>) -> Result<()> {
|
||||
assert!(ctx.accounts.token.mint == ctx.accounts.mint.key());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_with_token_program(_ctx: Context<TestInitTokenWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_composite_payer(ctx: Context<TestCompositePayer>) -> Result<()> {
|
||||
ctx.accounts.composite.data.data = 1;
|
||||
ctx.accounts.data.udata = 2;
|
||||
|
@ -192,6 +200,10 @@ pub mod misc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_with_token_program(_ctx: Context<TestInitAssociatedTokenWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_validate_associated_token(
|
||||
_ctx: Context<TestValidateAssociatedToken>,
|
||||
) -> Result<()> {
|
||||
|
@ -237,16 +249,32 @@ pub mod misc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_mint_if_needed_with_token_program(
|
||||
_ctx: Context<TestInitMintIfNeededWithTokenProgram>
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_if_needed(_ctx: Context<TestInitTokenIfNeeded>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_token_if_needed_with_token_program(_ctx: Context<TestInitTokenIfNeededWithTokenProgram>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_if_needed(
|
||||
_ctx: Context<TestInitAssociatedTokenIfNeeded>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_init_associated_token_if_needed_with_token_program(
|
||||
_ctx: Context<TestInitAssociatedTokenIfNeededWithTokenProgram>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_with_space(_ctx: Context<InitWithSpace>, _data: u16) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -317,6 +345,10 @@ pub mod misc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_only_token_program_constraint(_ctx: Context<TestOnlyTokenProgramConstraint>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_mint_constraint(_ctx: Context<TestMintConstraint>, _decimals: u8) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -347,7 +379,17 @@ pub mod misc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_mint_only_token_program_constraint(
|
||||
_ctx: Context<TestMintOnlyTokenProgramConstraint>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_associated_constraint(_ctx: Context<TestAssociatedToken>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_associated_token_with_token_program_constraint(_ctx: Context<TestAssociatedTokenWithTokenProgramConstraint>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -901,6 +901,68 @@ const miscTest = (
|
|||
assert.strictEqual(account2.idata.toNumber(), 3);
|
||||
});
|
||||
|
||||
it("Can create a random mint account with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMintWithTokenProgram({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
mintTokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
const client = new Token(
|
||||
program.provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const mintAccount = await client.getMintInfo();
|
||||
assert.strictEqual(mintAccount.decimals, 6);
|
||||
assert.isTrue(
|
||||
mintAccount.mintAuthority.equals(provider.wallet.publicKey)
|
||||
);
|
||||
assert.isTrue(
|
||||
mintAccount.freezeAuthority.equals(provider.wallet.publicKey)
|
||||
);
|
||||
const accInfo = await program.provider.connection.getAccountInfo(
|
||||
newMint.publicKey
|
||||
);
|
||||
assert.strictEqual(accInfo.owner.toString(), TOKEN_PROGRAM_ID.toString());
|
||||
});
|
||||
|
||||
it("Can create a random token account with token program", async () => {
|
||||
const token = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitTokenWithTokenProgram({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
|
||||
const client = new Token(
|
||||
program.provider.connection,
|
||||
mint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const account = await client.getAccountInfo(token.publicKey);
|
||||
// @ts-expect-error
|
||||
assert.strictEqual(account.state, 1);
|
||||
assert.strictEqual(account.amount.toNumber(), 0);
|
||||
assert.isTrue(account.isInitialized);
|
||||
assert.strictEqual(
|
||||
account.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(account.mint.toString(), mint.publicKey.toString());
|
||||
});
|
||||
|
||||
describe("associated_token constraints", () => {
|
||||
let associatedToken = null;
|
||||
// apparently cannot await here so doing it in the 'it' statements
|
||||
|
@ -942,6 +1004,64 @@ const miscTest = (
|
|||
assert.isTrue(account.mint.equals(localClient.publicKey));
|
||||
});
|
||||
|
||||
it("Can create an associated token account with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
newMint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedTokenWithTokenProgram({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
associatedTokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const token = new Token(
|
||||
program.provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const ataAccount = await token.getAccountInfo(associatedToken);
|
||||
// @ts-expect-error
|
||||
assert.strictEqual(ataAccount.state, 1);
|
||||
assert.strictEqual(ataAccount.amount.toNumber(), 0);
|
||||
assert.isTrue(ataAccount.isInitialized);
|
||||
assert.strictEqual(
|
||||
ataAccount.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
ataAccount.mint.toString(),
|
||||
newMint.publicKey.toString()
|
||||
);
|
||||
const rawAta = await provider.connection.getAccountInfo(
|
||||
associatedToken
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAta.owner.toBase58(),
|
||||
TOKEN_PROGRAM_ID.toBase58()
|
||||
);
|
||||
});
|
||||
|
||||
it("Can use fetchNullable() on accounts with only a balance", async () => {
|
||||
const account = anchor.web3.Keypair.generate();
|
||||
|
||||
|
@ -1028,6 +1148,106 @@ const miscTest = (
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("associated_token constraints (no init) - Can make with associated_token::token_program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedToken({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [],
|
||||
});
|
||||
await program.rpc.testAssociatedTokenWithTokenProgramConstraint({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
authority: provider.wallet.publicKey,
|
||||
associatedTokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const account = await provider.connection.getAccountInfo(
|
||||
associatedToken
|
||||
);
|
||||
assert.strictEqual(
|
||||
account.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("associated_token constraints (no init) - throws if associated_token::token_program mismatch", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedToken({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [],
|
||||
});
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testAssociatedTokenWithTokenProgramConstraint({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
authority: provider.wallet.publicKey,
|
||||
associatedTokenTokenProgram: fakeTokenProgram.publicKey,
|
||||
},
|
||||
});
|
||||
assert.isTrue(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2023);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintAssociatedTokenTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("Can fetch all accounts of a given type", async () => {
|
||||
|
@ -1253,6 +1473,300 @@ const miscTest = (
|
|||
assert.isUndefined(miscIdl.constants.find((c) => c.name === "NO_IDL"));
|
||||
});
|
||||
|
||||
it("init_if_needed creates mint account if not exists", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
|
||||
await program.rpc.testInitMintIfNeeded(6, {
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
mintAuthority: provider.wallet.publicKey,
|
||||
freezeAuthority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
|
||||
const mintAccount = await mintClient.getMintInfo();
|
||||
assert.strictEqual(mintAccount.decimals, 6);
|
||||
assert.strictEqual(
|
||||
mintAccount.mintAuthority.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
mintAccount.freezeAuthority.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(mintAccount.supply.toNumber(), 0);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
newMint.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed creates mint account if not exists with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
|
||||
await program.rpc.testInitMintIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
mintTokenProgram: TOKEN_PROGRAM_ID,
|
||||
mintAuthority: provider.wallet.publicKey,
|
||||
freezeAuthority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
|
||||
const mintAccount = await mintClient.getMintInfo();
|
||||
assert.strictEqual(mintAccount.decimals, 6);
|
||||
assert.strictEqual(
|
||||
mintAccount.mintAuthority.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
mintAccount.freezeAuthority.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(mintAccount.supply.toNumber(), 0);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
newMint.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed creates token account if not exists", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const newToken = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitTokenIfNeeded({
|
||||
accounts: {
|
||||
token: newToken.publicKey,
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newToken],
|
||||
});
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const tokenAccount = await mintClient.getAccountInfo(newToken.publicKey);
|
||||
assert.strictEqual(tokenAccount.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
tokenAccount.mint.toString(),
|
||||
newMint.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
tokenAccount.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
newToken.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed creates token account if not exists with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const newToken = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: newToken.publicKey,
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newToken],
|
||||
});
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const tokenAccount = await mintClient.getAccountInfo(newToken.publicKey);
|
||||
assert.strictEqual(tokenAccount.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
tokenAccount.mint.toString(),
|
||||
newMint.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
tokenAccount.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
newToken.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed creates associated token account if not exists", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
newMint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedTokenIfNeeded({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const ataAccount = await mintClient.getAccountInfo(associatedToken);
|
||||
assert.strictEqual(ataAccount.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
ataAccount.mint.toString(),
|
||||
newMint.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
ataAccount.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
associatedToken
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed creates associated token account if not exists with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
newMint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
associatedTokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
|
||||
const mintClient = new Token(
|
||||
provider.connection,
|
||||
newMint.publicKey,
|
||||
TOKEN_PROGRAM_ID,
|
||||
wallet.payer
|
||||
);
|
||||
const ataAccount = await mintClient.getAccountInfo(associatedToken);
|
||||
assert.strictEqual(ataAccount.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
ataAccount.mint.toString(),
|
||||
newMint.publicKey.toString()
|
||||
);
|
||||
assert.strictEqual(
|
||||
ataAccount.owner.toString(),
|
||||
provider.wallet.publicKey.toString()
|
||||
);
|
||||
const rawAccount = await provider.connection.getAccountInfo(
|
||||
associatedToken
|
||||
);
|
||||
assert.strictEqual(
|
||||
rawAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("init_if_needed throws if account exists but is not owned by the expected program", async () => {
|
||||
const newAcc = await anchor.web3.PublicKey.findProgramAddress(
|
||||
[utf8.encode("hello")],
|
||||
|
@ -1447,6 +1961,68 @@ const miscTest = (
|
|||
}
|
||||
});
|
||||
|
||||
it("init_if_needed pass if mint exists with token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
await program.rpc.testInitMintIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
mintTokenProgram: TOKEN_PROGRAM_ID,
|
||||
mintAuthority: provider.wallet.publicKey,
|
||||
freezeAuthority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
});
|
||||
|
||||
it("init_if_needed throws if mint exists but has the wrong token program", async () => {
|
||||
const newMint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testInitMintIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
mint: newMint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
mintTokenProgram: fakeTokenProgram.publicKey,
|
||||
mintAuthority: provider.wallet.publicKey,
|
||||
freezeAuthority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [newMint],
|
||||
});
|
||||
expect(false).to.be.true;
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2022);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintMintTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("init_if_needed throws if token exists but has the wrong owner", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
|
@ -1546,6 +2122,92 @@ const miscTest = (
|
|||
}
|
||||
});
|
||||
|
||||
it("init_if_needed pass if token exists with token program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const token = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitToken({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
|
||||
await program.rpc.testInitTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
});
|
||||
|
||||
it("init_if_needed throws if token exists but has the wrong token program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const token = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitToken({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testInitTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenTokenProgram: fakeTokenProgram.publicKey,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
expect(false).to.be.true;
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2021);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintTokenTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("init_if_needed throws if associated token exists but has the wrong owner", async () => {
|
||||
const mint = Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
|
@ -1636,7 +2298,6 @@ const miscTest = (
|
|||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
console.log("InitAssocToken:", txn);
|
||||
|
||||
try {
|
||||
await program.rpc.testInitAssociatedTokenIfNeeded({
|
||||
|
@ -1722,6 +2383,104 @@ const miscTest = (
|
|||
}
|
||||
});
|
||||
|
||||
it("init_if_needed pass if associated token exists with token program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedToken({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
await program.rpc.testInitAssociatedTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
associatedTokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("init_if_needed throws if associated token exists but has the wrong token program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const associatedToken = await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
mint.publicKey,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await program.rpc.testInitAssociatedToken({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testInitAssociatedTokenIfNeededWithTokenProgram({
|
||||
accounts: {
|
||||
token: associatedToken,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
associatedTokenTokenProgram: fakeTokenProgram.publicKey,
|
||||
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
expect(false).to.be.true;
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2023);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintAssociatedTokenTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Can use multidimensional array", async () => {
|
||||
const array2d = new Array(10).fill(new Array(10).fill(99));
|
||||
const data = anchor.web3.Keypair.generate();
|
||||
|
@ -1991,6 +2750,45 @@ const miscTest = (
|
|||
assert.isTrue(account.mint.equals(mint.publicKey));
|
||||
});
|
||||
|
||||
it("Token Constraint Test(no init) - Can make only token::token_program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const token = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitToken({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
await program.rpc.testOnlyTokenProgramConstraint({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
tokenTokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const account = await provider.connection.getAccountInfo(
|
||||
token.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
account.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("Token Constraint Test(no init) - throws if token::mint mismatch", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
|
@ -2132,6 +2930,48 @@ const miscTest = (
|
|||
}
|
||||
});
|
||||
|
||||
it("Token Constraint Test(no init) - throws if token::token_program mismatch", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
const token = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitToken({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [token],
|
||||
});
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testOnlyTokenProgramConstraint({
|
||||
accounts: {
|
||||
token: token.publicKey,
|
||||
tokenTokenProgram: fakeTokenProgram.publicKey,
|
||||
},
|
||||
});
|
||||
assert.isTrue(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2021);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintTokenTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Mint Constraint Test(no init) - mint::decimals, mint::authority, mint::freeze_authority", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
|
@ -2264,6 +3104,38 @@ const miscTest = (
|
|||
}
|
||||
});
|
||||
|
||||
it("Mint Constraint Test(no init) - throws if mint::token_program mismatch", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
const fakeTokenProgram = Keypair.generate();
|
||||
try {
|
||||
await program.rpc.testMintOnlyTokenProgramConstraint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
mintTokenProgram: fakeTokenProgram.publicKey,
|
||||
},
|
||||
});
|
||||
assert.isTrue(false);
|
||||
} catch (_err) {
|
||||
assert.isTrue(_err instanceof AnchorError);
|
||||
const err: AnchorError = _err;
|
||||
assert.strictEqual(err.error.errorCode.number, 2022);
|
||||
assert.strictEqual(
|
||||
err.error.errorCode.code,
|
||||
"ConstraintMintTokenProgram"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("Mint Constraint Test(no init) - can write only mint::decimals", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
|
@ -2385,6 +3257,34 @@ const miscTest = (
|
|||
mintAccount.freezeAuthority.equals(provider.wallet.publicKey)
|
||||
);
|
||||
});
|
||||
|
||||
it("Mint Constraint Test(no init) - can write only mint::token_program", async () => {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
await program.rpc.testInitMint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
payer: provider.wallet.publicKey,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [mint],
|
||||
});
|
||||
|
||||
await program.rpc.testMintOnlyTokenProgramConstraint({
|
||||
accounts: {
|
||||
mint: mint.publicKey,
|
||||
mintTokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
const mintAccount = await provider.connection.getAccountInfo(
|
||||
mint.publicKey
|
||||
);
|
||||
assert.strictEqual(
|
||||
mintAccount.owner.toString(),
|
||||
TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
});
|
||||
|
||||
it("check versioned transaction is now available", async () => {
|
||||
let thisTx = new VersionedTransaction(
|
||||
new Message({
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"pyth",
|
||||
"realloc",
|
||||
"spl/token-proxy",
|
||||
"spl/token-wrapper",
|
||||
"swap",
|
||||
"system-accounts",
|
||||
"sysvars",
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[programs.localnet]
|
||||
token_wrapper = "4ZPcGU8MX8oL2u1EtErHzixAbgNBNeE9yoYq3kKMqnAy"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -t 1000000 tests/*.ts"
|
||||
|
||||
[features]
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.mainnet-beta.solana.com"
|
||||
|
||||
[[test.validator.clone]]
|
||||
address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "token-wrapper",
|
||||
"version": "0.27.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=11"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "anchor test"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "token-wrapper"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.60"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "token_wrapper"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
anchor-spl = { path = "../../../../../spl" }
|
||||
spl-token-2022 = { version = "0.5.0", features = ["no-entrypoint"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,309 @@
|
|||
//! An example program where users can wrap tokens
|
||||
//! Source tokens are deposited into a vault in exchange for wrapped tokens at a 1:1 ratio
|
||||
//! This is a stateless implementation which relies on PDAs for security
|
||||
//!
|
||||
//! Initializer initializes a new wrapper:
|
||||
//! - SPL token/token-2022 mint (X) the deposit tokens the wrapper will receive
|
||||
//! - SPL token/token-2022 mint (Y) the wrapped tokens returned for each deposit token
|
||||
//!
|
||||
//! Once this wrapper is initialised:
|
||||
//! 1. Users can call the wrap function to deposit X and mint Y wrapped tokens
|
||||
//! 2. Users can call the unwrap function to burn Y and withdraw X unwrapped tokens
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token_interface::{
|
||||
self, Mint, TokenAccount, TokenInterface,
|
||||
};
|
||||
|
||||
declare_id!("4ZPcGU8MX8oL2u1EtErHzixAbgNBNeE9yoYq3kKMqnAy");
|
||||
|
||||
#[program]
|
||||
pub mod token_wrapper {
|
||||
use super::*;
|
||||
|
||||
pub const WRAPPER_AUTH_SEED: &[u8] = b"wrapr";
|
||||
pub const WRAPPER_VAULT_SEED: &[u8] = b"vault";
|
||||
|
||||
pub fn initialize(
|
||||
ctx: Context<Initialize>,
|
||||
initializer_amount: u64,
|
||||
) -> Result<()> {
|
||||
// deposit into vault
|
||||
token_interface::transfer_checked(
|
||||
CpiContext::new(
|
||||
ctx.accounts.deposit_token_program.to_account_info(),
|
||||
token_interface::TransferChecked {
|
||||
from: ctx.accounts.initializer_deposit_token_account.to_account_info(),
|
||||
mint: ctx.accounts.deposit_mint.to_account_info(),
|
||||
to: ctx.accounts.deposit_token_vault.to_account_info(),
|
||||
authority: ctx.accounts.initializer.to_account_info(),
|
||||
},
|
||||
),
|
||||
initializer_amount,
|
||||
ctx.accounts.deposit_mint.decimals,
|
||||
)?;
|
||||
|
||||
// mint wrapped tokens
|
||||
let inner_seeds = [
|
||||
WRAPPER_AUTH_SEED,
|
||||
ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
||||
ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
||||
&[*ctx.bumps.get("wrapper_authority").unwrap()],
|
||||
];
|
||||
let signer_seeds = &[&inner_seeds[..]];
|
||||
token_interface::mint_to(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.wrapped_token_program.to_account_info(),
|
||||
token_interface::MintTo {
|
||||
mint: ctx.accounts.wrapped_mint.to_account_info(),
|
||||
to: ctx.accounts.initializer_wrapped_token_account.to_account_info(),
|
||||
authority: ctx.accounts.wrapper_authority.to_account_info(),
|
||||
},
|
||||
signer_seeds,
|
||||
),
|
||||
initializer_amount,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wrap(ctx: Context<Wrap>, wrap_amount: u64) -> Result<()> {
|
||||
// deposit into vault
|
||||
token_interface::transfer_checked(
|
||||
CpiContext::new(
|
||||
ctx.accounts.deposit_token_program.to_account_info(),
|
||||
token_interface::TransferChecked {
|
||||
from: ctx.accounts.user_deposit_token_account.to_account_info(),
|
||||
mint: ctx.accounts.deposit_mint.to_account_info(),
|
||||
to: ctx.accounts.deposit_token_vault.to_account_info(),
|
||||
authority: ctx.accounts.signer.to_account_info(),
|
||||
},
|
||||
),
|
||||
wrap_amount,
|
||||
ctx.accounts.deposit_mint.decimals,
|
||||
)?;
|
||||
|
||||
// mint wrapped tokens
|
||||
let inner_seeds = [
|
||||
WRAPPER_AUTH_SEED,
|
||||
ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
||||
ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
||||
&[*ctx.bumps.get("wrapper_authority").unwrap()],
|
||||
];
|
||||
let signer_seeds = &[&inner_seeds[..]];
|
||||
token_interface::mint_to(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.wrapped_token_program.to_account_info(),
|
||||
token_interface::MintTo {
|
||||
mint: ctx.accounts.wrapped_mint.to_account_info(),
|
||||
to: ctx.accounts.user_wrapped_token_account.to_account_info(),
|
||||
authority: ctx.accounts.wrapper_authority.to_account_info(),
|
||||
},
|
||||
signer_seeds,
|
||||
),
|
||||
wrap_amount,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unwrap(ctx: Context<Unwrap>, unwrap_amount: u64) -> Result<()> {
|
||||
// burn wrapped tokens
|
||||
token_interface::burn(
|
||||
CpiContext::new(
|
||||
ctx.accounts.wrapped_token_program.to_account_info(),
|
||||
token_interface::Burn {
|
||||
mint: ctx.accounts.wrapped_mint.to_account_info(),
|
||||
from: ctx.accounts.user_wrapped_token_account.to_account_info(),
|
||||
authority: ctx.accounts.signer.to_account_info(),
|
||||
},
|
||||
),
|
||||
unwrap_amount,
|
||||
)?;
|
||||
|
||||
// withdraw from vault
|
||||
let inner_seeds = [
|
||||
WRAPPER_AUTH_SEED,
|
||||
ctx.accounts.deposit_mint.to_account_info().key.as_ref(),
|
||||
ctx.accounts.wrapped_mint.to_account_info().key.as_ref(),
|
||||
&[*ctx.bumps.get("wrapper_authority").unwrap()],
|
||||
];
|
||||
let signer_seeds = &[&inner_seeds[..]];
|
||||
token_interface::transfer_checked(
|
||||
CpiContext::new_with_signer(
|
||||
ctx.accounts.deposit_token_program.to_account_info(),
|
||||
token_interface::TransferChecked {
|
||||
from: ctx.accounts.deposit_token_vault.to_account_info(),
|
||||
mint: ctx.accounts.deposit_mint.to_account_info(),
|
||||
to: ctx.accounts.user_deposit_token_account.to_account_info(),
|
||||
authority: ctx.accounts.wrapper_authority.to_account_info(),
|
||||
},
|
||||
signer_seeds
|
||||
),
|
||||
unwrap_amount,
|
||||
ctx.accounts.deposit_mint.decimals,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(initializer_amount: u64)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(mut)]
|
||||
pub initializer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mint::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
#[account(init,
|
||||
payer = initializer,
|
||||
mint::decimals = deposit_mint.decimals,
|
||||
mint::authority = wrapper_authority,
|
||||
mint::token_program = wrapped_token_program,
|
||||
)]
|
||||
pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
/// Program-owned vault to store deposited tokens
|
||||
#[account(init,
|
||||
seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
payer = initializer,
|
||||
token::mint = deposit_mint,
|
||||
token::authority = wrapper_authority,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
/// User token account to deposit tokens from
|
||||
#[account(mut,
|
||||
constraint = initializer_deposit_token_account.amount >= initializer_amount,
|
||||
token::mint = deposit_mint,
|
||||
token::authority = initializer,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub initializer_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
/// User token account to send wrapped tokens to
|
||||
#[account(init,
|
||||
payer = initializer,
|
||||
token::mint = wrapped_mint,
|
||||
token::authority = initializer,
|
||||
token::token_program = wrapped_token_program,
|
||||
)]
|
||||
pub initializer_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
/// CHECK: PDA owned by the program
|
||||
#[account(mut,
|
||||
seeds = [WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
pub wrapper_authority: AccountInfo<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub deposit_token_program: Interface<'info, TokenInterface>,
|
||||
pub wrapped_token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(wrap_amount: u64)]
|
||||
pub struct Wrap<'info> {
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mint::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
#[account(mut,
|
||||
mint::authority = wrapper_authority,
|
||||
mint::token_program = wrapped_token_program,
|
||||
)]
|
||||
pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
#[account(mut,
|
||||
seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
token::mint = deposit_mint,
|
||||
token::authority = wrapper_authority,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
#[account(mut,
|
||||
constraint = user_deposit_token_account.amount >= wrap_amount,
|
||||
token::mint = deposit_mint,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub user_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
#[account(mut,
|
||||
token::mint = wrapped_mint,
|
||||
token::token_program = wrapped_token_program,
|
||||
)]
|
||||
pub user_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
/// CHECK: PDA owned by the program
|
||||
#[account(mut,
|
||||
seeds = [WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
pub wrapper_authority: AccountInfo<'info>,
|
||||
|
||||
pub deposit_token_program: Interface<'info, TokenInterface>,
|
||||
pub wrapped_token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(unwrap_amount: u64)]
|
||||
pub struct Unwrap<'info> {
|
||||
#[account(mut)]
|
||||
pub signer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mint::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
#[account(mut,
|
||||
mint::token_program = wrapped_token_program,
|
||||
mint::authority = wrapper_authority,
|
||||
)]
|
||||
pub wrapped_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
|
||||
#[account(mut,
|
||||
seeds = [WRAPPER_VAULT_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
token::mint = deposit_mint,
|
||||
token::authority = wrapper_authority,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub deposit_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
#[account(mut,
|
||||
token::mint = deposit_mint,
|
||||
token::token_program = deposit_token_program,
|
||||
)]
|
||||
pub user_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
#[account(mut,
|
||||
constraint = user_wrapped_token_account.amount >= unwrap_amount,
|
||||
token::mint = wrapped_mint,
|
||||
token::token_program = wrapped_token_program,
|
||||
)]
|
||||
pub user_wrapped_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
|
||||
/// CHECK: PDA owned by the program
|
||||
#[account(mut,
|
||||
seeds = [crate::token_wrapper::WRAPPER_AUTH_SEED, deposit_mint.key().as_ref(), wrapped_mint.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
pub wrapper_authority: AccountInfo<'info>,
|
||||
|
||||
pub deposit_token_program: Interface<'info, TokenInterface>,
|
||||
pub wrapped_token_program: Interface<'info, TokenInterface>,
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { Program, BN, IdlAccounts } from "@coral-xyz/anchor";
|
||||
import { PublicKey, Keypair, SystemProgram } from "@solana/web3.js";
|
||||
import { TOKEN_PROGRAM_ID, Token } from "@solana/spl-token";
|
||||
import { assert } from "chai";
|
||||
import { TokenWrapper } from "../target/types/token_wrapper";
|
||||
|
||||
describe("wrapper", () => {
|
||||
const provider = anchor.AnchorProvider.env();
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey(
|
||||
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
||||
);
|
||||
const TEST_TOKEN_PROGRAM_IDS = [
|
||||
[TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID],
|
||||
[TOKEN_2022_PROGRAM_ID, TOKEN_2022_PROGRAM_ID],
|
||||
[TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID],
|
||||
[TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID],
|
||||
];
|
||||
const program = anchor.workspace.TokenWrapper as Program<TokenWrapper>;
|
||||
|
||||
let depositMint: Token = null;
|
||||
let wrappedMint: Token = null;
|
||||
let initializerDepositTokenAccount: PublicKey = null;
|
||||
let userWrappedTokenAccount: PublicKey = null;
|
||||
let userDepositTokenAccount: PublicKey = null;
|
||||
let depositTokenVault: PublicKey = null;
|
||||
let wrapperAuthority: PublicKey = null;
|
||||
|
||||
const wrapAmount = 1000;
|
||||
const initializerAmount = 500;
|
||||
|
||||
const payer = Keypair.generate();
|
||||
const mintAuthority = Keypair.generate();
|
||||
|
||||
TEST_TOKEN_PROGRAM_IDS.forEach((tokenProgramIds) => {
|
||||
const [depositTokenProgram, wrappedTokenProgram] = tokenProgramIds;
|
||||
let name;
|
||||
if (depositTokenProgram === wrappedTokenProgram) {
|
||||
name = wrappedTokenProgram === TOKEN_PROGRAM_ID ? "token" : "token-2022";
|
||||
} else {
|
||||
name =
|
||||
wrappedTokenProgram === TOKEN_PROGRAM_ID
|
||||
? "mixed-wrapped-token"
|
||||
: "mixed-wrapped-token-2022";
|
||||
}
|
||||
describe(name, () => {
|
||||
it("Initialise wrapper", async () => {
|
||||
// Airdropping tokens to a payer.
|
||||
await provider.connection.confirmTransaction(
|
||||
await provider.connection.requestAirdrop(
|
||||
payer.publicKey,
|
||||
10000000000
|
||||
),
|
||||
"confirmed"
|
||||
);
|
||||
|
||||
depositMint = await Token.createMint(
|
||||
provider.connection,
|
||||
payer,
|
||||
mintAuthority.publicKey,
|
||||
null,
|
||||
6,
|
||||
depositTokenProgram
|
||||
);
|
||||
|
||||
initializerDepositTokenAccount = await depositMint.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await depositMint.mintTo(
|
||||
initializerDepositTokenAccount,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
initializerAmount
|
||||
);
|
||||
|
||||
const wrappedMintKP = new Keypair();
|
||||
const initializerWrappedTokenAccountKP = new Keypair();
|
||||
|
||||
// Get the PDA that is assigned authority to the deposit vault account
|
||||
const [_wrapperAuthority, _] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from(anchor.utils.bytes.utf8.encode("wrapr")),
|
||||
depositMint.publicKey.toBuffer(),
|
||||
wrappedMintKP.publicKey.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
wrapperAuthority = _wrapperAuthority;
|
||||
|
||||
// Get the deposit vault account PDA
|
||||
const [_depositTokenVault, __] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from(anchor.utils.bytes.utf8.encode("vault")),
|
||||
depositMint.publicKey.toBuffer(),
|
||||
wrappedMintKP.publicKey.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
depositTokenVault = _depositTokenVault;
|
||||
|
||||
await program.rpc.initialize(new BN(initializerAmount), {
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
depositMint: depositMint.publicKey,
|
||||
wrappedMint: wrappedMintKP.publicKey,
|
||||
depositTokenVault,
|
||||
initializerDepositTokenAccount,
|
||||
initializerWrappedTokenAccount:
|
||||
initializerWrappedTokenAccountKP.publicKey,
|
||||
wrapperAuthority,
|
||||
systemProgram: SystemProgram.programId,
|
||||
depositTokenProgram,
|
||||
wrappedTokenProgram,
|
||||
},
|
||||
signers: [wrappedMintKP, initializerWrappedTokenAccountKP],
|
||||
});
|
||||
|
||||
wrappedMint = new Token(
|
||||
provider.connection,
|
||||
wrappedMintKP.publicKey,
|
||||
wrappedTokenProgram,
|
||||
payer
|
||||
);
|
||||
|
||||
let _initializerDepositTokenAccount = await depositMint.getAccountInfo(
|
||||
initializerDepositTokenAccount
|
||||
);
|
||||
let _initializerWrappedTokenAccount = await wrappedMint.getAccountInfo(
|
||||
initializerWrappedTokenAccountKP.publicKey
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
_initializerDepositTokenAccount.amount.toNumber(),
|
||||
0
|
||||
);
|
||||
assert.strictEqual(
|
||||
_initializerWrappedTokenAccount.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
});
|
||||
|
||||
it("Wrap", async () => {
|
||||
userDepositTokenAccount = await depositMint.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
userWrappedTokenAccount = await wrappedMint.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await depositMint.mintTo(
|
||||
userDepositTokenAccount,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
wrapAmount
|
||||
);
|
||||
|
||||
await program.rpc.wrap(new BN(wrapAmount), {
|
||||
accounts: {
|
||||
signer: provider.wallet.publicKey,
|
||||
depositMint: depositMint.publicKey,
|
||||
wrappedMint: wrappedMint.publicKey,
|
||||
depositTokenVault: depositTokenVault,
|
||||
userDepositTokenAccount,
|
||||
userWrappedTokenAccount,
|
||||
wrapperAuthority,
|
||||
depositTokenProgram,
|
||||
wrappedTokenProgram,
|
||||
},
|
||||
});
|
||||
|
||||
let _userDepositTokenAccount = await depositMint.getAccountInfo(
|
||||
userDepositTokenAccount
|
||||
);
|
||||
let _userWrappedTokenAccount = await wrappedMint.getAccountInfo(
|
||||
userWrappedTokenAccount
|
||||
);
|
||||
|
||||
assert.strictEqual(_userDepositTokenAccount.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
_userWrappedTokenAccount.amount.toNumber(),
|
||||
wrapAmount
|
||||
);
|
||||
});
|
||||
|
||||
it("Unwrap", async () => {
|
||||
await program.rpc.unwrap(new BN(wrapAmount - 1), {
|
||||
accounts: {
|
||||
signer: provider.wallet.publicKey,
|
||||
depositMint: depositMint.publicKey,
|
||||
wrappedMint: wrappedMint.publicKey,
|
||||
depositTokenVault: depositTokenVault,
|
||||
userDepositTokenAccount,
|
||||
userWrappedTokenAccount,
|
||||
wrapperAuthority,
|
||||
depositTokenProgram,
|
||||
wrappedTokenProgram,
|
||||
},
|
||||
});
|
||||
|
||||
let _userDepositTokenAccount = await depositMint.getAccountInfo(
|
||||
userDepositTokenAccount
|
||||
);
|
||||
let _userWrappedTokenAccount = await wrappedMint.getAccountInfo(
|
||||
userWrappedTokenAccount
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
_userDepositTokenAccount.amount.toNumber(),
|
||||
wrapAmount - 1
|
||||
);
|
||||
assert.strictEqual(_userWrappedTokenAccount.amount.toNumber(), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai", "node"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue