Browse Source

lang, examples: Add mint initialization constraints (#562)

Henry-E 4 years ago
parent
commit
fef207dc83

+ 5 - 0
CHANGELOG.md

@@ -14,6 +14,11 @@ incremented for features.
 ### Features
 ### Features
 
 
 * lang: Ignore `Unnamed` structs instead of panic ([#605](https://github.com/project-serum/anchor/pull/605)).
 * 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
 ## [0.13.2] - 2021-08-11
 
 

+ 2 - 2
Cargo.lock

@@ -2863,7 +2863,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "serum-common"
 name = "serum-common"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/project-serum/serum-dex#576e5d2ef2a1669fbd889f13b97b4f552554e334"
+source = "git+https://github.com/project-serum/serum-dex#b977df6c9c89d0600396fe0c9244ea0ee6fafb5a"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "arrayref",
  "arrayref",
@@ -2881,7 +2881,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "serum_dex"
 name = "serum_dex"
 version = "0.4.0"
 version = "0.4.0"
-source = "git+https://github.com/project-serum/serum-dex#9f776c45fa37ec80109ea1f46461a75125f3f334"
+source = "git+https://github.com/project-serum/serum-dex#b977df6c9c89d0600396fe0c9244ea0ee6fafb5a"
 dependencies = [
 dependencies = [
  "arrayref",
  "arrayref",
  "bincode",
  "bincode",

+ 4 - 6
examples/cfo/programs/cfo/src/lib.rs

@@ -292,15 +292,14 @@ pub struct CreateOfficer<'info> {
     officer: ProgramAccount<'info, Officer>,
     officer: ProgramAccount<'info, Officer>,
     #[account(
     #[account(
         init,
         init,
-        token = mint,
+        token::mint = mint,
         associated = officer, with = b"vault",
         associated = officer, with = b"vault",
-        space = TokenAccount::LEN,
         payer = authority,
         payer = authority,
     )]
     )]
     srm_vault: CpiAccount<'info, TokenAccount>,
     srm_vault: CpiAccount<'info, TokenAccount>,
     #[account(
     #[account(
         init,
         init,
-        token = mint,
+        token::mint = mint,
         associated = officer, with = b"stake",
         associated = officer, with = b"stake",
         space = TokenAccount::LEN,
         space = TokenAccount::LEN,
         payer = authority,
         payer = authority,
@@ -308,9 +307,8 @@ pub struct CreateOfficer<'info> {
     stake: CpiAccount<'info, TokenAccount>,
     stake: CpiAccount<'info, TokenAccount>,
     #[account(
     #[account(
         init,
         init,
-        token = mint,
+        token::mint = mint,
         associated = officer, with = b"treasury",
         associated = officer, with = b"treasury",
-        space = TokenAccount::LEN,
         payer = authority,
         payer = authority,
     )]
     )]
     treasury: CpiAccount<'info, TokenAccount>,
     treasury: CpiAccount<'info, TokenAccount>,
@@ -337,7 +335,7 @@ pub struct CreateOfficerToken<'info> {
     officer: ProgramAccount<'info, Officer>,
     officer: ProgramAccount<'info, Officer>,
     #[account(
     #[account(
         init,
         init,
-        token = mint,
+        token::mint = mint,
         associated = officer, with = mint,
         associated = officer, with = mint,
         space = TokenAccount::LEN,
         space = TokenAccount::LEN,
         payer = payer,
         payer = payer,

+ 7 - 0
examples/misc/package.json

@@ -0,0 +1,7 @@
+{
+  "dependencies": {
+    "@project-serum/anchor": "^0.11.1",
+    "@solana/spl-token": "^0.1.6",
+    "mocha": "^9.0.3"
+  }
+}

+ 12 - 6
examples/misc/programs/misc/src/context.rs

@@ -5,18 +5,24 @@ use anchor_spl::token::{Mint, TokenAccount};
 use misc2::misc2::MyState as Misc2State;
 use misc2::misc2::MyState as Misc2State;
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-#[instruction(nonce: u8)]
+#[instruction(token_bump: u8, mint_bump: u8)]
 pub struct TestTokenSeedsInit<'info> {
 pub struct TestTokenSeedsInit<'info> {
     #[account(
     #[account(
         init,
         init,
-        token = mint,
-        authority = authority,
-        seeds = [b"my-token-seed".as_ref(), &[nonce]],
+        seeds = [b"my-mint-seed".as_ref(), &[mint_bump]],
         payer = authority,
         payer = authority,
-        space = TokenAccount::LEN,
+        mint::decimals = 6,
+        mint::authority = authority,
     )]
     )]
-    pub my_pda: CpiAccount<'info, TokenAccount>,
     pub mint: CpiAccount<'info, Mint>,
     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 authority: AccountInfo<'info>,
     pub system_program: AccountInfo<'info>,
     pub system_program: AccountInfo<'info>,
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,

+ 5 - 1
examples/misc/programs/misc/src/lib.rs

@@ -143,7 +143,11 @@ pub mod misc {
         Ok(())
         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(())
         Ok(())
     }
     }
 
 

+ 14 - 12
examples/misc/tests/misc.js

@@ -458,22 +458,18 @@ describe("misc", () => {
   });
   });
 
 
   it("Can create a token account from seeds pda", async () => {
   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"))],
       [Buffer.from(anchor.utils.bytes.utf8.encode("my-token-seed"))],
       program.programId
       program.programId
     );
     );
-    await program.rpc.testTokenSeedsInit(bump, {
+    await program.rpc.testTokenSeedsInit(token_bump, mint_bump, {
       accounts: {
       accounts: {
         myPda,
         myPda,
-        mint: mint.publicKey,
+        mint,
         authority: program.provider.wallet.publicKey,
         authority: program.provider.wallet.publicKey,
         systemProgram: anchor.web3.SystemProgram.programId,
         systemProgram: anchor.web3.SystemProgram.programId,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
@@ -481,12 +477,18 @@ describe("misc", () => {
       },
       },
     });
     });
 
 
-    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.state === 1);
     assert.ok(account.amount.toNumber() === 0);
     assert.ok(account.amount.toNumber() === 0);
     assert.ok(account.isInitialized);
     assert.ok(account.isInitialized);
     assert.ok(account.owner.equals(program.provider.wallet.publicKey));
     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 () => {
   it("Can execute a fallback function", async () => {

+ 68 - 52
lang/syn/src/codegen/accounts/constraints.rs

@@ -464,27 +464,6 @@ pub fn generate_pda(
     let field = &f.ident;
     let field = &f.ident;
     let (account_ty, account_wrapper_ty, is_zero_copy) = parse_ty(f);
     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 {
     let nonce_assignment = match assign_nonce {
         false => quote! {},
         false => quote! {},
         true => match &f.ty {
         true => match &f.ty {
@@ -525,7 +504,6 @@ pub fn generate_pda(
     match kind {
     match kind {
         PdaKind::Token { owner, mint } => quote! {
         PdaKind::Token { owner, mint } => quote! {
             let #field: #combined_account_ty = {
             let #field: #combined_account_ty = {
-                #space
                 #payer
                 #payer
                 #seeds_constraint
                 #seeds_constraint
 
 
@@ -534,64 +512,102 @@ pub fn generate_pda(
                     .minimum_balance(anchor_spl::token::TokenAccount::LEN)
                     .minimum_balance(anchor_spl::token::TokenAccount::LEN)
                     .max(1)
                     .max(1)
                     .saturating_sub(#field.to_account_info().lamports());
                     .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::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,
                         #field.to_account_info().key,
+                        required_lamports,
                         anchor_spl::token::TokenAccount::LEN as u64,
                         anchor_spl::token::TokenAccount::LEN as u64,
+                        token_program.to_account_info().key,
                     ),
                     ),
                     &[
                     &[
+                        payer.to_account_info(),
                         #field.to_account_info(),
                         #field.to_account_info(),
-                        system_program.clone(),
+                        system_program.to_account_info().clone(),
                     ],
                     ],
                     &[&#seeds_with_nonce[..]],
                     &[&#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(
                 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(),
                         #field.to_account_info(),
-                        system_program.to_account_info(),
+                        system_program.to_account_info().clone(),
                     ],
                     ],
                     &[&#seeds_with_nonce[..]],
                     &[&#seeds_with_nonce[..]],
                 )?;
                 )?;
 
 
-                // Initialize the token 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::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(),
                     rent: rent.to_account_info(),
                 };
                 };
                 let cpi_ctx = CpiContext::new(cpi_program, accounts);
                 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(
                 anchor_lang::CpiAccount::try_from_init(
                     &#field.to_account_info(),
                     &#field.to_account_info(),
                 )?
                 )?
             };
             };
         },
         },
         PdaKind::Program { owner } => {
         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,
             // Owner of the account being created. If not specified,
             // default to the currently executing program.
             // default to the currently executing program.
             let owner = match owner {
             let owner = match owner {

+ 13 - 0
lang/syn/src/lib.rs

@@ -341,6 +341,8 @@ pub enum ConstraintToken {
     Address(Context<ConstraintAddress>),
     Address(Context<ConstraintAddress>),
     TokenMint(Context<ConstraintTokenMint>),
     TokenMint(Context<ConstraintTokenMint>),
     TokenAuthority(Context<ConstraintTokenAuthority>),
     TokenAuthority(Context<ConstraintTokenAuthority>),
+    MintAuthority(Context<ConstraintMintAuthority>),
+    MintDecimals(Context<ConstraintMintDecimals>),
     Bump(Context<ConstraintTokenBump>),
     Bump(Context<ConstraintTokenBump>),
 }
 }
 
 
@@ -448,6 +450,7 @@ pub struct ConstraintAssociatedSpace {
 pub enum PdaKind {
 pub enum PdaKind {
     Program { owner: Option<Expr> },
     Program { owner: Option<Expr> },
     Token { owner: Expr, mint: Expr },
     Token { owner: Expr, mint: Expr },
+    Mint { owner: Expr, decimals: Expr },
 }
 }
 
 
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
@@ -465,6 +468,16 @@ pub struct ConstraintTokenAuthority {
     auth: Expr,
     auth: Expr,
 }
 }
 
 
+#[derive(Debug, Clone)]
+pub struct ConstraintMintAuthority {
+    mint_auth: Expr,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMintDecimals {
+    decimals: Expr,
+}
+
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
 pub struct ConstraintTokenBump {
 pub struct ConstraintTokenBump {
     bump: Expr,
     bump: Expr,

+ 168 - 28
lang/syn/src/parser/accounts/constraints.rs

@@ -67,6 +67,60 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
         "executable" => {
         "executable" => {
             ConstraintToken::Executable(Context::new(ident.span(), ConstraintExecutable {}))
             ConstraintToken::Executable(Context::new(ident.span(), ConstraintExecutable {}))
         }
         }
+        "mint" => {
+            stream.parse::<Token![:]>()?;
+            stream.parse::<Token![:]>()?;
+            let kw = stream.call(Ident::parse_any)?.to_string();
+            stream.parse::<Token![=]>()?;
+
+            let span = ident
+                .span()
+                .join(stream.span())
+                .unwrap_or_else(|| ident.span());
+
+            match kw.as_str() {
+                "authority" => ConstraintToken::MintAuthority(Context::new(
+                    span,
+                    ConstraintMintAuthority {
+                        mint_auth: stream.parse()?,
+                    },
+                )),
+                "decimals" => ConstraintToken::MintDecimals(Context::new(
+                    span,
+                    ConstraintMintDecimals {
+                        decimals: stream.parse()?,
+                    },
+                )),
+                _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+            }
+        }
+        "token" => {
+            stream.parse::<Token![:]>()?;
+            stream.parse::<Token![:]>()?;
+            let kw = stream.call(Ident::parse_any)?.to_string();
+            stream.parse::<Token![=]>()?;
+
+            let span = ident
+                .span()
+                .join(stream.span())
+                .unwrap_or_else(|| ident.span());
+
+            match kw.as_str() {
+                "mint" => ConstraintToken::TokenMint(Context::new(
+                    span,
+                    ConstraintTokenMint {
+                        mint: stream.parse()?,
+                    },
+                )),
+                "authority" => ConstraintToken::TokenAuthority(Context::new(
+                    span,
+                    ConstraintTokenAuthority {
+                        auth: stream.parse()?,
+                    },
+                )),
+                _ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
+            }
+        }
         _ => {
         _ => {
             stream.parse::<Token![=]>()?;
             stream.parse::<Token![=]>()?;
             let span = ident
             let span = ident
@@ -164,18 +218,6 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
                         address: stream.parse()?,
                         address: stream.parse()?,
                     },
                     },
                 )),
                 )),
-                "token" => ConstraintToken::TokenMint(Context::new(
-                    ident.span(),
-                    ConstraintTokenMint {
-                        mint: stream.parse()?,
-                    },
-                )),
-                "authority" => ConstraintToken::TokenAuthority(Context::new(
-                    ident.span(),
-                    ConstraintTokenAuthority {
-                        auth: stream.parse()?,
-                    },
-                )),
                 "bump" => ConstraintToken::Bump(Context::new(
                 "bump" => ConstraintToken::Bump(Context::new(
                     ident.span(),
                     ident.span(),
                     ConstraintTokenBump {
                     ConstraintTokenBump {
@@ -212,6 +254,8 @@ pub struct ConstraintGroupBuilder<'ty> {
     pub address: Option<Context<ConstraintAddress>>,
     pub address: Option<Context<ConstraintAddress>>,
     pub token_mint: Option<Context<ConstraintTokenMint>>,
     pub token_mint: Option<Context<ConstraintTokenMint>>,
     pub token_authority: Option<Context<ConstraintTokenAuthority>>,
     pub token_authority: Option<Context<ConstraintTokenAuthority>>,
+    pub mint_authority: Option<Context<ConstraintMintAuthority>>,
+    pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
     pub bump: Option<Context<ConstraintTokenBump>>,
     pub bump: Option<Context<ConstraintTokenBump>>,
 }
 }
 
 
@@ -238,6 +282,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             address: None,
             address: None,
             token_mint: None,
             token_mint: None,
             token_authority: None,
             token_authority: None,
+            mint_authority: None,
+            mint_decimals: None,
             bump: None,
             bump: None,
         }
         }
     }
     }
@@ -260,6 +306,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                     .replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
                     .replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
             }
             }
         }
         }
+
+        // Seeds.
         if let Some(i) = &self.seeds {
         if let Some(i) = &self.seeds {
             if self.init.is_some() && self.associated_payer.is_none() {
             if self.init.is_some() && self.associated_payer.is_none() {
                 return Err(ParseError::new(
                 return Err(ParseError::new(
@@ -269,7 +317,15 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             }
             }
         }
         }
 
 
+        // Token.
         if let Some(token_mint) = &self.token_mint {
         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() || (self.associated.is_none() && self.seeds.is_none()) {
             if self.init.is_none() || (self.associated.is_none() && self.seeds.is_none()) {
                 return Err(ParseError::new(
                 return Err(ParseError::new(
                     token_mint.span(),
                     token_mint.span(),
@@ -277,6 +333,45 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                 ));
                 ));
             }
             }
         }
         }
+        if let Some(token_authority) = &self.token_authority {
+            if self.token_mint.is_none() {
+                return Err(ParseError::new(
+                    token_authority.span(),
+                    "token authority must be provided if token mint 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",
+                ));
+            }
+        }
+
+        // SPL Space.
+        if self.init.is_some()
+            && self.seeds.is_some()
+            && self.token_mint.is_some()
+            && (self.mint_authority.is_some() || self.token_authority.is_some())
+            && self.associated_space.is_some()
+        {
+            return Err(ParseError::new(
+                self.associated_space.as_ref().unwrap().span(),
+                "space is not required for initializing an spl account",
+            ));
+        }
 
 
         let ConstraintGroupBuilder {
         let ConstraintGroupBuilder {
             f_ty: _,
             f_ty: _,
@@ -299,6 +394,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             address,
             address,
             token_mint,
             token_mint,
             token_authority,
             token_authority,
+            mint_authority,
+            mint_decimals,
             bump,
             bump,
         } = self;
         } = self;
 
 
@@ -343,21 +440,34 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                         seeds: c.into_inner().seeds,
                         seeds: c.into_inner().seeds,
                         payer: into_inner!(associated_payer.clone()).map(|a| a.target),
                         payer: into_inner!(associated_payer.clone()).map(|a| a.target),
                         space: associated_space.clone().map(|s| s.space.clone()),
                         space: associated_space.clone().map(|s| s.space.clone()),
-                        kind: match &token_mint {
-                            None => PdaKind::Program {
-                                owner: pda_owner.clone(),
-                            },
-                            Some(tm) => PdaKind::Token {
-                                mint: tm.clone().into_inner().mint,
-                                owner: match &token_authority {
-                                    Some(a) => a.clone().into_inner().auth,
-                                    None => return Err(ParseError::new(
-                                        tm.span(),
-                                        "authority must be provided to initialize a token program derived address"
-                                    )),
+                        kind: if let Some(tm) = &token_mint {
+                                PdaKind::Token {
+                                    mint: tm.clone().into_inner().mint,
+                                    owner: match &token_authority {
+                                        Some(a) => a.clone().into_inner().auth,
+                                        None => return Err(ParseError::new(
+                                            tm.span(),
+                                            "authority must be provided to initialize a token program derived address"
+                                            )),
+                                        },
+                                    }
+                                } else if let Some(d) = &mint_decimals {
+                                    PdaKind::Mint {
+                                        decimals: d.clone().into_inner().decimals,
+                                        owner: match &mint_authority {
+                                            Some(a) => a.clone().into_inner().mint_auth,
+                                            None => return Err(ParseError::new(
+                                                d.span(),
+                                                "authority must be provided to initialize a mint program derived address"
+                                            ))
+
+                                        }
+                                    }
+                                } else {
+                                    PdaKind::Program {
+                                        owner: pda_owner.clone(),
+                                    }
                                 },
                                 },
-                            },
-                        },
                         bump: into_inner!(bump).map(|b| b.bump),
                         bump: into_inner!(bump).map(|b| b.bump),
                     })
                     })
                 })
                 })
@@ -407,6 +517,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             ConstraintToken::Address(c) => self.add_address(c),
             ConstraintToken::Address(c) => self.add_address(c),
             ConstraintToken::TokenAuthority(c) => self.add_token_authority(c),
             ConstraintToken::TokenAuthority(c) => self.add_token_authority(c),
             ConstraintToken::TokenMint(c) => self.add_token_mint(c),
             ConstraintToken::TokenMint(c) => self.add_token_mint(c),
+            ConstraintToken::MintAuthority(c) => self.add_mint_authority(c),
+            ConstraintToken::MintDecimals(c) => self.add_mint_decimals(c),
             ConstraintToken::Bump(c) => self.add_bump(c),
             ConstraintToken::Bump(c) => self.add_bump(c),
         }
         }
     }
     }
@@ -484,16 +596,44 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                 "token authority already provided",
                 "token authority already provided",
             ));
             ));
         }
         }
-        if self.token_mint.is_none() {
+        if self.init.is_none() {
             return Err(ParseError::new(
             return Err(ParseError::new(
                 c.span(),
                 c.span(),
-                "token must bne provided before authority",
+                "init must be provided before token authority",
             ));
             ));
         }
         }
         self.token_authority.replace(c);
         self.token_authority.replace(c);
         Ok(())
         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"));
+        }
+        if self.init.is_none() {
+            return Err(ParseError::new(
+                c.span(),
+                "init must be provided before mint authority",
+            ));
+        }
+        self.mint_authority.replace(c);
+        Ok(())
+    }
+
+    fn add_mint_decimals(&mut self, c: Context<ConstraintMintDecimals>) -> ParseResult<()> {
+        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(())
+    }
+
     fn add_mut(&mut self, c: Context<ConstraintMut>) -> ParseResult<()> {
     fn add_mut(&mut self, c: Context<ConstraintMut>) -> ParseResult<()> {
         if self.mutable.is_some() {
         if self.mutable.is_some() {
             return Err(ParseError::new(c.span(), "mut already provided"));
             return Err(ParseError::new(c.span(), "mut already provided"));

+ 4 - 0
spl/src/token.rs

@@ -256,6 +256,10 @@ impl Deref for TokenAccount {
 #[derive(Clone)]
 #[derive(Clone)]
 pub struct Mint(spl_token::state::Mint);
 pub struct Mint(spl_token::state::Mint);
 
 
+impl Mint {
+    pub const LEN: usize = spl_token::state::Mint::LEN;
+}
+
 impl anchor_lang::AccountDeserialize for Mint {
 impl anchor_lang::AccountDeserialize for Mint {
     fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
     fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
         Mint::try_deserialize_unchecked(buf)
         Mint::try_deserialize_unchecked(buf)