Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token_program constraint to token, mint, and associated token accounts #2460

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/reusable-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions lang/derive/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ use syn::parse_macro_input;
/// <tr>
/// <td>
/// <code>#[account(token::mint = &lt;target_account&gt;, token::authority = &lt;target_account&gt;)]</code>
/// <br><br>
/// <code>#[account(token::mint = &lt;target_account&gt;, token::authority = &lt;target_account&gt;, token::token_program = &lt;target_account&gt;)]</code>
/// </td>
/// <td>
/// Can be used as a check or with <code>init</code> to create a token
Expand Down Expand Up @@ -546,6 +548,8 @@ use syn::parse_macro_input;
/// <tr>
/// <td>
/// <code>#[account(associated_token::mint = &lt;target_account&gt;, associated_token::authority = &lt;target_account&gt;)]</code>
/// <br><br>
/// <code>#[account(associated_token::mint = &lt;target_account&gt;, associated_token::authority = &lt;target_account&gt;, associated_token::token_program = &lt;target_account&gt;)]</code>
/// </td>
/// <td>
/// Can be used as a standalone as a check or with <code>init</code> to create an associated token
Expand Down Expand Up @@ -580,6 +584,48 @@ use syn::parse_macro_input;
/// pub system_program: Program<'info, System>
/// </pre>
/// </td>
/// </tr><tr>
/// <td>
/// <code>#[account(*::token_program = &lt;target_account&gt;)]</code>
/// </td>
/// <td>
/// The <code>token_program</code> can optionally be overridden.
/// <br><br>
/// Example:
/// <pre>
/// use anchor_spl::{mint, token::{TokenAccount, Mint, Token}};
/// ...&#10;
/// #[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>
Expand Down
11 changes: 11 additions & 0 deletions lang/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 80 additions & 14 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,18 +497,26 @@ fn generate_constraint_init_group(

// Optional check idents
let system_program = &quote! {system_program};
let token_program = &quote! {token_program};
let associated_token_program = &quote! {associated_token_program};
let rent = &quote! {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! {
Expand All @@ -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,
);
Expand All @@ -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(),
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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)?;
Expand All @@ -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));
Expand All @@ -634,15 +658,20 @@ 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),
None => quote! {},
};

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! {
Expand All @@ -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,
);
Expand All @@ -676,15 +705,16 @@ 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

// Create the account with the system program.
#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(),
};
Expand All @@ -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
};
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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
}
}
}
Expand Down
Loading