lang: allow `token::...` and `mint::...` to be used as checks without init (#1505)

Co-authored-by: Paul Schaaf <paulsimonschaaf@gmail.com>
This commit is contained in:
Anan 2022-04-12 06:25:07 +09:00 committed by GitHub
parent 537d470954
commit f5dffe6490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 903 additions and 102 deletions

View File

@ -436,8 +436,9 @@ use syn::parse_macro_input;
/// <code>#[account(token::mint = &lt;target_account&gt;, token::authority = &lt;target_account&gt;)]</code>
/// </td>
/// <td>
/// Can currently only be used with <code>init</code> to create a token
/// account with the given mint address and authority.
/// Can be used as a check or with <code>init</code> to create a token
/// account with the given mint address and authority.<br>
/// When used as a check, it's possible to only specify a subset of the constraints.
/// <br><br>
/// Example:
/// <pre>
@ -466,9 +467,10 @@ use syn::parse_macro_input;
/// <code>#[account(mint::authority = &lt;target_account&gt;, mint::decimals = &lt;expr&gt;, mint::freeze_authority = &lt;target_account&gt;)]</code>
/// </td>
/// <td>
/// Can currently only be used with <code>init</code> to create a mint
/// Can be used as a check or with <code>init</code> to create a mint
/// account with the given mint decimals and mint authority.<br>
/// The freeze authority is optional.
/// The freeze authority is optional when used with <code>init</code>.<br>
/// When used as a check, it's possible to only specify a subset of the constraints.
/// <br><br>
/// Example:
/// <pre>

View File

@ -57,6 +57,8 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
close,
address,
associated_token,
token_account,
mint,
} = c_group.clone();
let mut constraints = Vec::new();
@ -100,6 +102,12 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
if let Some(c) = address {
constraints.push(Constraint::Address(c));
}
if let Some(c) = token_account {
constraints.push(Constraint::TokenAccount(c));
}
if let Some(c) = mint {
constraints.push(Constraint::Mint(c));
}
constraints
}
@ -120,6 +128,8 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
Constraint::Close(c) => generate_constraint_close(f, c),
Constraint::Address(c) => generate_constraint_address(f, c),
Constraint::AssociatedToken(c) => generate_constraint_associated_token(f, c),
Constraint::TokenAccount(c) => generate_constraint_token_account(f, c),
Constraint::Mint(c) => generate_constraint_mint(f, c),
}
}
@ -682,6 +692,63 @@ fn generate_constraint_associated_token(
}
}
fn generate_constraint_token_account(
f: &Field,
c: &ConstraintTokenAccountGroup,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let authority_check = match &c.authority {
Some(authority) => {
quote! { if #name.owner != #authority.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); } }
}
None => quote! {},
};
let mint_check = match &c.mint {
Some(mint) => {
quote! { if #name.mint != #mint.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenMint.into()); } }
}
None => quote! {},
};
quote! {
#authority_check
#mint_check
}
}
fn generate_constraint_mint(f: &Field, c: &ConstraintTokenMintGroup) -> proc_macro2::TokenStream {
let name = &f.ident;
let decimal_check = match &c.decimals {
Some(decimals) => quote! {
if #name.decimals != #decimals {
return Err(anchor_lang::error::ErrorCode::ConstraintMintDecimals.into());
}
},
None => quote! {},
};
let mint_authority_check = match &c.mint_authority {
Some(mint_authority) => quote! {
if #name.mint_authority != anchor_lang::solana_program::program_option::COption::Some(anchor_lang::Key::key(&#mint_authority)) {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority.into());
}
},
None => quote! {},
};
let freeze_authority_check = match &c.freeze_authority {
Some(freeze_authority) => quote! {
if #name.freeze_authority != anchor_lang::solana_program::program_option::COption::Some(anchor_lang::Key::key(&#freeze_authority)) {
return Err(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority.into());
}
},
None => quote! {},
};
quote! {
#decimal_check
#mint_authority_check
#freeze_authority_check
}
}
// Generated code to create an account with with system program with the
// given `space` amount of data, owned by `owner`.
//

View File

@ -587,6 +587,8 @@ pub struct ConstraintGroup {
close: Option<ConstraintClose>,
address: Option<ConstraintAddress>,
associated_token: Option<ConstraintAssociatedToken>,
token_account: Option<ConstraintTokenAccountGroup>,
mint: Option<ConstraintTokenMintGroup>,
}
impl ConstraintGroup {
@ -628,6 +630,8 @@ pub enum Constraint {
State(ConstraintState),
Close(ConstraintClose),
Address(ConstraintAddress),
TokenAccount(ConstraintTokenAccountGroup),
Mint(ConstraintTokenMintGroup),
}
// Constraint token is a single keyword in a `#[account(<TOKEN>)]` attribute.
@ -832,6 +836,19 @@ pub struct ConstraintAssociatedToken {
pub mint: Expr,
}
#[derive(Debug, Clone)]
pub struct ConstraintTokenAccountGroup {
pub mint: Option<Expr>,
pub authority: Option<Expr>,
}
#[derive(Debug, Clone)]
pub struct ConstraintTokenMintGroup {
pub decimals: Option<Expr>,
pub mint_authority: Option<Expr>,
pub freeze_authority: Option<Expr>,
}
// Syntaxt context object for preserving metadata about the inner item.
#[derive(Debug, Clone)]
pub struct Context<T> {

View File

@ -424,6 +424,42 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
));
}
}
// TokenAccount.
if let Some(token_mint) = &self.token_mint {
if self.token_authority.is_none() {
return Err(ParseError::new(
token_mint.span(),
"when initializing, token authority must be provided if token mint is",
));
}
}
if let Some(token_authority) = &self.token_authority {
if self.token_mint.is_none() {
return Err(ParseError::new(
token_authority.span(),
"when initializing, token mint must be provided if token authority is",
));
}
}
// Mint.
if let Some(mint_decimals) = &self.mint_decimals {
if self.mint_authority.is_none() {
return Err(ParseError::new(
mint_decimals.span(),
"when initializing, mint authority must be provided if mint decimals is",
));
}
}
if let Some(mint_authority) = &self.mint_authority {
if self.mint_decimals.is_none() {
return Err(ParseError::new(
mint_authority.span(),
"when initializing, mint decimals must be provided if mint authority is",
));
}
}
}
// Zero.
@ -462,49 +498,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
}
// Token.
if let Some(token_mint) = &self.token_mint {
if self.token_authority.is_none() {
return Err(ParseError::new(
token_mint.span(),
"token authority must be provided if token mint is",
));
}
if self.init.is_none() {
return Err(ParseError::new(
token_mint.span(),
"init is required for a pda token",
));
}
}
if let Some(token_authority) = &self.token_authority {
if self.token_mint.is_none() {
return Err(ParseError::new(
token_authority.span(),
"token mint must be provided if token authority is",
));
}
}
// Mint.
if let Some(mint_decimals) = &self.mint_decimals {
if self.mint_authority.is_none() {
return Err(ParseError::new(
mint_decimals.span(),
"mint authority must be provided if mint decimals is",
));
}
}
if let Some(mint_authority) = &self.mint_authority {
if self.mint_decimals.is_none() {
return Err(ParseError::new(
mint_authority.span(),
"mint decimals must be provided if mint authority is",
));
}
}
// Space.
if let Some(i) = &self.init {
let initializing_token_program_acc = self.token_mint.is_some()
@ -600,6 +593,31 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
_ => None,
};
let token_account = match (&token_mint, &token_authority) {
(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),
}),
};
let mint = match (&mint_decimals, &mint_authority, &mint_freeze_authority) {
(None, None, None) => None,
_ => Some(ConstraintTokenMintGroup {
decimals: mint_decimals
.as_ref()
.map(|a| a.clone().into_inner().decimals),
mint_authority: mint_authority
.as_ref()
.map(|a| a.clone().into_inner().mint_auth),
freeze_authority: mint_freeze_authority
.as_ref()
.map(|a| a.clone().into_inner().mint_freeze_auth),
}),
};
Ok(ConstraintGroup {
init: init.as_ref().map(|i| Ok(ConstraintInitGroup {
if_needed: i.if_needed,
@ -654,6 +672,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
address: into_inner!(address),
associated_token: if !is_init { associated_token } else { None },
seeds,
token_account: if !is_init {token_account} else {None},
mint: if !is_init {mint} else {None},
})
}
@ -694,6 +714,48 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.zeroed.is_some() {
return Err(ParseError::new(c.span(), "zeroed already provided"));
}
if self.token_mint.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before token mint",
));
}
if self.token_authority.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before token authority",
));
}
if self.mint_authority.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint authority",
));
}
if self.mint_freeze_authority.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint freeze authority",
));
}
if self.mint_decimals.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint decimals",
));
}
if self.associated_token_mint.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before associated token mint",
));
}
if self.associated_token_authority.is_some() {
return Err(ParseError::new(
c.span(),
"init must be provided before associated token authority",
));
}
self.init.replace(c);
Ok(())
}
@ -751,12 +813,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
"associated token mint already provided",
));
}
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before token",
));
}
self.token_mint.replace(c);
Ok(())
}
@ -823,12 +879,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
"token authority already provided",
));
}
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before token authority",
));
}
self.token_authority.replace(c);
Ok(())
}
@ -857,12 +907,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.mint_authority.is_some() {
return Err(ParseError::new(c.span(), "mint authority already provided"));
}
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint authority",
));
}
self.mint_authority.replace(c);
Ok(())
}
@ -877,12 +921,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
"mint freeze_authority already provided",
));
}
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint freeze_authority",
));
}
self.mint_freeze_authority.replace(c);
Ok(())
}
@ -891,12 +929,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.mint_decimals.is_some() {
return Err(ParseError::new(c.span(), "mint decimals already provided"));
}
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before mint decimals",
));
}
self.mint_decimals.replace(c);
Ok(())
}

View File

@ -16,7 +16,6 @@ pub struct TestTokenSeedsInit<'info> {
payer = authority,
mint::decimals = 6,
mint::authority = authority,
)]
pub mint: Account<'info, Mint>,
#[account(
@ -26,7 +25,6 @@ pub struct TestTokenSeedsInit<'info> {
payer = authority,
token::mint = mint,
token::authority = authority,
)]
pub my_pda: Account<'info, TokenAccount>,
#[account(mut)]
@ -41,10 +39,9 @@ pub struct TestTokenSeedsInit<'info> {
pub struct TestInitAssociatedToken<'info> {
#[account(
init,
payer = payer,
associated_token::mint = mint,
payer = payer,
associated_token::authority = payer,
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
@ -476,3 +473,106 @@ pub struct TestUnsafeFieldSafetyErrors<'info> {
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct TestConstraintToken<'info> {
#[account(
token::mint = mint,
token::authority = payer
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub payer: Signer<'info>,
}
#[derive(Accounts)]
pub struct TestAuthorityConstraint<'info> {
#[account(
token::mint = mint,
token::authority = fake_authority
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub fake_authority: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestOnlyAuthorityConstraint<'info> {
#[account(
token::authority = payer
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub payer: Signer<'info>,
}
#[derive(Accounts)]
pub struct TestOnlyMintConstraint<'info> {
#[account(
token::mint = mint,
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
}
#[derive(Accounts)]
#[instruction(decimals: u8)]
pub struct TestMintConstraint<'info> {
#[account(
mint::decimals = decimals,
mint::authority = mint_authority,
mint::freeze_authority = freeze_authority
)]
pub mint: Account<'info, Mint>,
pub mint_authority: AccountInfo<'info>,
pub freeze_authority: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(decimals: u8)]
pub struct TestMintOnlyDecimalsConstraint<'info> {
#[account(
mint::decimals = decimals,
)]
pub mint: Account<'info, Mint>,
}
#[derive(Accounts)]
pub struct TestMintAuthorityConstraint<'info> {
#[account(
mint::authority = mint_authority,
mint::freeze_authority = freeze_authority
)]
pub mint: Account<'info, Mint>,
pub mint_authority: AccountInfo<'info>,
pub freeze_authority: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestMintOneAuthorityConstraint<'info> {
#[account(
mint::authority = mint_authority,
)]
pub mint: Account<'info, Mint>,
pub mint_authority: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(decimals: u8)]
pub struct TestMintMissMintAuthConstraint<'info> {
#[account(
mint::decimals = decimals,
mint::freeze_authority = freeze_authority,
)]
pub mint: Account<'info, Mint>,
pub freeze_authority: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestAssociatedToken<'info> {
#[account(
associated_token::mint = mint,
associated_token::authority = authority,
)]
pub token: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub authority: AccountInfo<'info>,
}

View File

@ -303,4 +303,54 @@ pub mod misc {
) -> Result<()> {
Ok(())
}
pub fn test_token_constraint(_ctx: Context<TestConstraintToken>) -> Result<()> {
Ok(())
}
pub fn test_token_auth_constraint(_ctx: Context<TestAuthorityConstraint>) -> Result<()> {
Ok(())
}
pub fn test_only_auth_constraint(_ctx: Context<TestOnlyAuthorityConstraint>) -> Result<()> {
Ok(())
}
pub fn test_only_mint_constraint(_ctx: Context<TestOnlyMintConstraint>) -> Result<()> {
Ok(())
}
pub fn test_mint_constraint(_ctx: Context<TestMintConstraint>, _decimals: u8) -> Result<()> {
Ok(())
}
pub fn test_mint_only_decimals_constraint(
_ctx: Context<TestMintOnlyDecimalsConstraint>,
_decimals: u8,
) -> Result<()> {
Ok(())
}
pub fn test_mint_only_auth_constraint(
_ctx: Context<TestMintAuthorityConstraint>,
) -> Result<()> {
Ok(())
}
pub fn test_mint_only_one_auth_constraint(
_ctx: Context<TestMintOneAuthorityConstraint>,
) -> Result<()> {
Ok(())
}
pub fn test_mint_miss_mint_auth_constraint(
_ctx: Context<TestMintMissMintAuthConstraint>,
_decimals: u8,
) -> Result<()> {
Ok(())
}
pub fn test_associated_constraint(_ctx: Context<TestAssociatedToken>) -> Result<()> {
Ok(())
}
}

View File

@ -14,7 +14,7 @@ import {
import { Misc } from "../target/types/misc";
import { Misc2 } from "../target/types/misc2";
const utf8 = anchor.utils.bytes.utf8;
const { assert } = require("chai");
const { assert, expect } = require("chai");
const nativeAssert = require("assert");
const miscIdl = require("../target/idl/misc.json");
@ -160,7 +160,7 @@ describe("misc", () => {
"Program data: jvbowsvlmkcJAAAA",
"Program data: zxM5neEnS1kBAgMEBQYHCAkK",
"Program data: g06Ei2GL1gIBAgMEBQYHCAkKCw==",
"Program 3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh consumed 5395 of 1400000 compute units",
"Program 3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh consumed 3983 of 1400000 compute units",
"Program 3TEqcc8xhrhdspwbvoamUJe2borm4Nr72JxL66k6rgrh success",
];
@ -232,7 +232,7 @@ describe("misc", () => {
solDest: data.publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (err) {
const errMsg = "A close constraint was violated";
assert.strictEqual(err.error.errorMessage, errMsg);
@ -266,7 +266,7 @@ describe("misc", () => {
).lamports;
// Retrieved rent exemption sol.
assert.ok(afterBalance > beforeBalance);
expect(afterBalance > beforeBalance).to.be.true;
const closedAccount = await program.provider.connection.getAccountInfo(
data.publicKey
@ -989,7 +989,7 @@ describe("misc", () => {
owner: anchor.web3.Keypair.generate().publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1028,7 +1028,7 @@ describe("misc", () => {
owner: anchor.web3.Keypair.generate().publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1057,7 +1057,7 @@ describe("misc", () => {
},
signers: [newAcc],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1091,7 +1091,7 @@ describe("misc", () => {
},
signers: [mint],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1125,7 +1125,7 @@ describe("misc", () => {
},
signers: [mint],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1159,7 +1159,7 @@ describe("misc", () => {
},
signers: [mint],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1206,7 +1206,7 @@ describe("misc", () => {
},
signers: [token],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1265,7 +1265,7 @@ describe("misc", () => {
},
signers: [token],
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1318,7 +1318,7 @@ describe("misc", () => {
authority: anchor.web3.Keypair.generate().publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1383,7 +1383,7 @@ describe("misc", () => {
authority: program.provider.wallet.publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1449,7 +1449,7 @@ describe("misc", () => {
authority: program.provider.wallet.publicKey,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1463,7 +1463,6 @@ describe("misc", () => {
await program.rpc.testMultidimensionalArray(array2d, {
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [data],
instructions: [
@ -1482,7 +1481,6 @@ describe("misc", () => {
await program.rpc.testMultidimensionalArrayConstSizes(array2d, {
accounts: {
data: data.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [data],
instructions: [
@ -1520,7 +1518,7 @@ describe("misc", () => {
second: secondPDA,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1535,7 +1533,7 @@ describe("misc", () => {
second: secondPDA,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1574,7 +1572,7 @@ describe("misc", () => {
second: secondPDA,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1589,7 +1587,7 @@ describe("misc", () => {
second: secondPDA,
},
});
assert.ok(false);
expect(false).to.be.true;
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
@ -1605,4 +1603,539 @@ describe("misc", () => {
});
});
});
describe("Token Constraint Test", () => {
it("Token Constraint Test(no init) - Can make token::mint and token::authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
await program.rpc.testTokenConstraint({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
},
});
const mintAccount = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const account = await mintAccount.getAccountInfo(token.publicKey);
assert.isTrue(account.owner.equals(program.provider.wallet.publicKey));
assert.isTrue(account.mint.equals(mint.publicKey));
});
it("Token Constraint Test(no init) - Can make only token::authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
await program.rpc.testOnlyAuthConstraint({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
},
});
const mintAccount = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const account = await mintAccount.getAccountInfo(token.publicKey);
assert.isTrue(account.owner.equals(program.provider.wallet.publicKey));
});
it("Token Constraint Test(no init) - Can make only token::mint", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
await program.rpc.testOnlyMintConstraint({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
},
});
const mintAccount = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const account = await mintAccount.getAccountInfo(token.publicKey);
assert.isTrue(account.mint.equals(mint.publicKey));
});
it("Token Constraint Test(no init) - throws if token::mint mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const mint1 = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint1.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint1],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
try {
await program.rpc.testTokenConstraint({
accounts: {
token: token.publicKey,
mint: mint1.publicKey,
payer: program.provider.wallet.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2014);
assert.strictEqual(err.error.errorCode.code, "ConstraintTokenMint");
}
});
it("Token Constraint Test(no init) - throws if token::authority mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
const fakeAuthority = Keypair.generate();
try {
await program.rpc.testTokenAuthConstraint({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
fakeAuthority: fakeAuthority.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2015);
assert.strictEqual(err.error.errorCode.code, "ConstraintTokenOwner");
}
});
it("Token Constraint Test(no init) - throws if both token::authority, token::mint mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const mint1 = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint1.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint1],
});
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
const fakeAuthority = Keypair.generate();
try {
await program.rpc.testTokenAuthConstraint({
accounts: {
token: token.publicKey,
mint: mint1.publicKey,
fakeAuthority: fakeAuthority.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2015);
assert.strictEqual(err.error.errorCode.code, "ConstraintTokenOwner");
}
});
it("Mint Constraint Test(no init) - mint::decimals, mint::authority, mint::freeze_authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
await program.rpc.testMintConstraint(6, {
accounts: {
mint: mint.publicKey,
mintAuthority: program.provider.wallet.publicKey,
freezeAuthority: program.provider.wallet.publicKey,
},
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.strictEqual(mintAccount.decimals, 6);
assert.isTrue(
mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
);
assert.isTrue(
mintAccount.freezeAuthority.equals(program.provider.wallet.publicKey)
);
});
it("Mint Constraint Test(no init) - throws if mint::decimals mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const fakeDecimal = 5;
try {
await program.rpc.testMintConstraint(fakeDecimal, {
accounts: {
mint: mint.publicKey,
mintAuthority: program.provider.wallet.publicKey,
freezeAuthority: program.provider.wallet.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2018);
assert.strictEqual(err.error.errorCode.code, "ConstraintMintDecimals");
}
});
it("Mint Constraint Test(no init) - throws if mint::mint_authority mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const fakeAuthority = Keypair.generate();
try {
await program.rpc.testMintConstraint(6, {
accounts: {
mint: mint.publicKey,
mintAuthority: fakeAuthority.publicKey,
freezeAuthority: program.provider.wallet.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2016);
assert.strictEqual(
err.error.errorCode.code,
"ConstraintMintMintAuthority"
);
}
});
it("Mint Constraint Test(no init) - throws if mint::freeze_authority mismatch", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const fakeAuthority = Keypair.generate();
try {
await program.rpc.testMintConstraint(6, {
accounts: {
mint: mint.publicKey,
mintAuthority: program.provider.wallet.publicKey,
freezeAuthority: fakeAuthority.publicKey,
},
});
assert.isTrue(false);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
assert.strictEqual(err.error.errorCode.number, 2017);
assert.strictEqual(
err.error.errorCode.code,
"ConstraintMintFreezeAuthority"
);
}
});
it("Mint Constraint Test(no init) - can write only mint::decimals", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
await program.rpc.testMintOnlyDecimalsConstraint(6, {
accounts: {
mint: mint.publicKey,
},
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.strictEqual(mintAccount.decimals, 6);
});
it("Mint Constraint Test(no init) - can write only mint::authority and mint::freeze_authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
await program.rpc.testMintOnlyAuthConstraint({
accounts: {
mint: mint.publicKey,
mintAuthority: program.provider.wallet.publicKey,
freezeAuthority: program.provider.wallet.publicKey,
},
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.isTrue(
mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
);
assert.isTrue(
mintAccount.freezeAuthority.equals(program.provider.wallet.publicKey)
);
});
it("Mint Constraint Test(no init) - can write only mint::authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
await program.rpc.testMintOnlyOneAuthConstraint({
accounts: {
mint: mint.publicKey,
mintAuthority: program.provider.wallet.publicKey,
},
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.isTrue(
mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
);
});
it("Mint Constraint Test(no init) - can write only mint::decimals and mint::freeze_authority", async () => {
const mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
await program.rpc.testMintMissMintAuthConstraint(6, {
accounts: {
mint: mint.publicKey,
freezeAuthority: program.provider.wallet.publicKey,
},
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.strictEqual(mintAccount.decimals, 6);
assert.isTrue(
mintAccount.freezeAuthority.equals(program.provider.wallet.publicKey)
);
});
});
});