Skip to content

Commit

Permalink
lang, examples: Add mint initialization constraints (coral-xyz#562)
Browse files Browse the repository at this point in the history
  • Loading branch information
Henry-E authored and WrRaThY committed Aug 15, 2021
1 parent f67c5ce commit 67ebc4b
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 107 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ incremented for features.
### Features

* lang: Ignore `Unnamed` structs instead of panic ([#605](https://github.com/project-serum/anchor/pull/605)).
* lang: Add constraints for initializing mint accounts as pdas, `#[account(init, seeds = [...], mint::decimals = <expr>, mint::authority = <expr>)]` ([#](https://github.com/project-serum/anchor/pull/562)).

### Breaking Changes

* lang: Change `#[account(init, seeds = [...], token = <expr>, authority = <expr>)]` to `#[account(init, token::mint = <expr> token::authority = <expr>)]` ([#](https://github.com/project-serum/anchor/pull/562)).

## [0.13.2] - 2021-08-11

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 4 additions & 6 deletions examples/cfo/programs/cfo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,25 +292,23 @@ pub struct CreateOfficer<'info> {
officer: ProgramAccount<'info, Officer>,
#[account(
init,
token = mint,
token::mint = mint,
associated = officer, with = b"vault",
space = TokenAccount::LEN,
payer = authority,
)]
srm_vault: CpiAccount<'info, TokenAccount>,
#[account(
init,
token = mint,
token::mint = mint,
associated = officer, with = b"stake",
space = TokenAccount::LEN,
payer = authority,
)]
stake: CpiAccount<'info, TokenAccount>,
#[account(
init,
token = mint,
token::mint = mint,
associated = officer, with = b"treasury",
space = TokenAccount::LEN,
payer = authority,
)]
treasury: CpiAccount<'info, TokenAccount>,
Expand All @@ -337,7 +335,7 @@ pub struct CreateOfficerToken<'info> {
officer: ProgramAccount<'info, Officer>,
#[account(
init,
token = mint,
token::mint = mint,
associated = officer, with = mint,
space = TokenAccount::LEN,
payer = payer,
Expand Down
7 changes: 7 additions & 0 deletions examples/misc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"@project-serum/anchor": "^0.11.1",
"@solana/spl-token": "^0.1.6",
"mocha": "^9.0.3"
}
}
18 changes: 12 additions & 6 deletions examples/misc/programs/misc/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ use anchor_spl::token::{Mint, TokenAccount};
use misc2::misc2::MyState as Misc2State;

#[derive(Accounts)]
#[instruction(nonce: u8)]
#[instruction(token_bump: u8, mint_bump: u8)]
pub struct TestTokenSeedsInit<'info> {
#[account(
init,
token = mint,
authority = authority,
seeds = [b"my-token-seed".as_ref(), &[nonce]],
seeds = [b"my-mint-seed".as_ref(), &[mint_bump]],
payer = authority,
space = TokenAccount::LEN,
mint::decimals = 6,
mint::authority = authority,
)]
pub my_pda: CpiAccount<'info, TokenAccount>,
pub mint: CpiAccount<'info, Mint>,
#[account(
init,
seeds = [b"my-token-seed".as_ref(), &[token_bump]],
payer = authority,
token::mint = mint,
token::authority = authority,
)]
pub my_pda: CpiAccount<'info, TokenAccount>,
pub authority: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
Expand Down
6 changes: 5 additions & 1 deletion examples/misc/programs/misc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ pub mod misc {
Ok(())
}

pub fn test_token_seeds_init(_ctx: Context<TestTokenSeedsInit>, _nonce: u8) -> ProgramResult {
pub fn test_token_seeds_init(
_ctx: Context<TestTokenSeedsInit>,
_token_bump: u8,
_mint_bump: u8,
) -> ProgramResult {
Ok(())
}

Expand Down
26 changes: 14 additions & 12 deletions examples/misc/tests/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,35 +458,37 @@ describe("misc", () => {
});

it("Can create a token account from seeds pda", async () => {
const mint = await Token.createMint(
program.provider.connection,
program.provider.wallet.payer,
program.provider.wallet.publicKey,
null,
0,
TOKEN_PROGRAM_ID
const [mint, mint_bump] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-mint-seed"))],
program.programId
);
const [myPda, bump] = await PublicKey.findProgramAddress(
const [myPda, token_bump] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-token-seed"))],
program.programId
);
await program.rpc.testTokenSeedsInit(bump, {
await program.rpc.testTokenSeedsInit(token_bump, mint_bump, {
accounts: {
myPda,
mint: mint.publicKey,
mint,
authority: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
tokenProgram: TOKEN_PROGRAM_ID,
},
});

const account = await mint.getAccountInfo(myPda);
const mintAccount = new Token(
program.provider.connection,
mint,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const account = await mintAccount.getAccountInfo(myPda);
assert.ok(account.state === 1);
assert.ok(account.amount.toNumber() === 0);
assert.ok(account.isInitialized);
assert.ok(account.owner.equals(program.provider.wallet.publicKey));
assert.ok(account.mint.equals(mint.publicKey));
assert.ok(account.mint.equals(mint));
});

it("Can execute a fallback function", async () => {
Expand Down
120 changes: 68 additions & 52 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,27 +464,6 @@ pub fn generate_pda(
let field = &f.ident;
let (account_ty, account_wrapper_ty, is_zero_copy) = parse_ty(f);

let space = match space {
// If no explicit space param was given, serialize the type to bytes
// and take the length (with +8 for the discriminator.)
None => match is_zero_copy {
false => {
quote! {
let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
}
}
true => {
quote! {
let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
}
}
},
// Explicit account size given. Use it.
Some(s) => quote! {
let space = #s;
},
};

let nonce_assignment = match assign_nonce {
false => quote! {},
true => match &f.ty {
Expand Down Expand Up @@ -525,7 +504,6 @@ pub fn generate_pda(
match kind {
PdaKind::Token { owner, mint } => quote! {
let #field: #combined_account_ty = {
#space
#payer
#seeds_constraint

Expand All @@ -534,64 +512,102 @@ pub fn generate_pda(
.minimum_balance(anchor_spl::token::TokenAccount::LEN)
.max(1)
.saturating_sub(#field.to_account_info().lamports());
if required_lamports > 0 {
anchor_lang::solana_program::program::invoke(
&anchor_lang::solana_program::system_instruction::transfer(
payer.to_account_info().key,
#field.to_account_info().key,
required_lamports,
),
&[
payer.to_account_info(),
#field.to_account_info(),
system_program.to_account_info().clone(),
],
)?;
}

// Allocate space.
// Create the token account with right amount of lamports and space, and the correct owner.
anchor_lang::solana_program::program::invoke_signed(
&anchor_lang::solana_program::system_instruction::allocate(
&anchor_lang::solana_program::system_instruction::create_account(
payer.to_account_info().key,
#field.to_account_info().key,
required_lamports,
anchor_spl::token::TokenAccount::LEN as u64,
token_program.to_account_info().key,
),
&[
payer.to_account_info(),
#field.to_account_info(),
system_program.clone(),
system_program.to_account_info().clone(),
],
&[&#seeds_with_nonce[..]],
)?;

// Assign to the spl token program.
let __ix = anchor_lang::solana_program::system_instruction::assign(
#field.to_account_info().key,
token_program.to_account_info().key,
);
// Initialize the token account.
let cpi_program = token_program.to_account_info();
let accounts = anchor_spl::token::InitializeAccount {
account: #field.to_account_info(),
mint: #mint.to_account_info(),
authority: #owner.to_account_info(),
rent: rent.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, accounts);
anchor_spl::token::initialize_account(cpi_ctx)?;
anchor_lang::CpiAccount::try_from_init(
&#field.to_account_info(),
)?
};
},
PdaKind::Mint { owner, decimals } => quote! {
let #field: #combined_account_ty = {
#payer
#seeds_constraint

// Fund the account for rent exemption.
let required_lamports = rent
.minimum_balance(anchor_spl::token::Mint::LEN)
.max(1)
.saturating_sub(#field.to_account_info().lamports());

// Create the token account with right amount of lamports and space, and the correct owner.
anchor_lang::solana_program::program::invoke_signed(
&__ix,
&anchor_lang::solana_program::system_instruction::create_account(
payer.to_account_info().key,
#field.to_account_info().key,
required_lamports,
anchor_spl::token::Mint::LEN as u64,
token_program.to_account_info().key,
),
&[
payer.to_account_info(),
#field.to_account_info(),
system_program.to_account_info(),
system_program.to_account_info().clone(),
],
&[&#seeds_with_nonce[..]],
)?;

// Initialize the token account.
// Initialize the mint account.
let cpi_program = token_program.to_account_info();
let accounts = anchor_spl::token::InitializeAccount {
account: #field.to_account_info(),
mint: #mint.to_account_info(),
authority: #owner.to_account_info(),
let accounts = anchor_spl::token::InitializeMint {
mint: #field.to_account_info(),
rent: rent.to_account_info(),
};
let cpi_ctx = CpiContext::new(cpi_program, accounts);
anchor_spl::token::initialize_account(cpi_ctx)?;
anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, None)?;
anchor_lang::CpiAccount::try_from_init(
&#field.to_account_info(),
)?
};
},
PdaKind::Program { owner } => {
let space = match space {
// If no explicit space param was given, serialize the type to bytes
// and take the length (with +8 for the discriminator.)
None => match is_zero_copy {
false => {
quote! {
let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
}
}
true => {
quote! {
let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
}
}
},
// Explicit account size given. Use it.
Some(s) => quote! {
let space = #s;
},
};

// Owner of the account being created. If not specified,
// default to the currently executing program.
let owner = match owner {
Expand Down
13 changes: 13 additions & 0 deletions lang/syn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ pub enum ConstraintToken {
Address(Context<ConstraintAddress>),
TokenMint(Context<ConstraintTokenMint>),
TokenAuthority(Context<ConstraintTokenAuthority>),
MintAuthority(Context<ConstraintMintAuthority>),
MintDecimals(Context<ConstraintMintDecimals>),
Bump(Context<ConstraintTokenBump>),
}

Expand Down Expand Up @@ -448,6 +450,7 @@ pub struct ConstraintAssociatedSpace {
pub enum PdaKind {
Program { owner: Option<Expr> },
Token { owner: Expr, mint: Expr },
Mint { owner: Expr, decimals: Expr },
}

#[derive(Debug, Clone)]
Expand All @@ -465,6 +468,16 @@ pub struct ConstraintTokenAuthority {
auth: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintMintAuthority {
mint_auth: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintMintDecimals {
decimals: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintTokenBump {
bump: Expr,
Expand Down
Loading

0 comments on commit 67ebc4b

Please sign in to comment.