Эх сурвалжийг харах

lang: Sends lamports to payer on init (#648)

Armani Ferrante 4 жил өмнө
parent
commit
6085eda9df

+ 122 - 0
examples/misc/tests/misc.js

@@ -386,6 +386,28 @@ describe("misc", () => {
     assert.ok(account.data === 3);
   });
 
+  it("Can init a random account prefunded", async () => {
+    const data = anchor.web3.Keypair.generate();
+    await program.rpc.testInit({
+      accounts: {
+        data: data.publicKey,
+        payer: program.provider.wallet.publicKey,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      },
+      signers: [data],
+      instructions: [
+        anchor.web3.SystemProgram.transfer({
+          fromPubkey: program.provider.wallet.publicKey,
+          toPubkey: data.publicKey,
+          lamports: 4039280,
+        }),
+      ],
+    });
+
+    const account = await program.account.dataI8.fetch(data.publicKey);
+    assert.ok(account.data === 3);
+  });
+
   it("Can init a random zero copy account", async () => {
     const data = anchor.web3.Keypair.generate();
     await program.rpc.testInitZeroCopy({
@@ -428,6 +450,38 @@ describe("misc", () => {
     );
   });
 
+  it("Can create a random mint account prefunded", async () => {
+    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],
+      instructions: [
+        anchor.web3.SystemProgram.transfer({
+          fromPubkey: program.provider.wallet.publicKey,
+          toPubkey: mint.publicKey,
+          lamports: 4039280,
+        }),
+      ],
+    });
+    const client = new Token(
+      program.provider.connection,
+      mint.publicKey,
+      TOKEN_PROGRAM_ID,
+      program.provider.wallet.payer
+    );
+    const mintAccount = await client.getMintInfo();
+    assert.ok(mintAccount.decimals === 6);
+    assert.ok(
+      mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
+    );
+  });
+
   it("Can create a random token account", async () => {
     const token = anchor.web3.Keypair.generate();
     await program.rpc.testInitToken({
@@ -454,4 +508,72 @@ describe("misc", () => {
     assert.ok(account.owner.equals(program.provider.wallet.publicKey));
     assert.ok(account.mint.equals(mint.publicKey));
   });
+
+  it("Can create a random token with prefunding", async () => {
+    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],
+      instructions: [
+        anchor.web3.SystemProgram.transfer({
+          fromPubkey: program.provider.wallet.publicKey,
+          toPubkey: token.publicKey,
+          lamports: 4039280,
+        }),
+      ],
+    });
+    const client = new Token(
+      program.provider.connection,
+      mint.publicKey,
+      TOKEN_PROGRAM_ID,
+      program.provider.wallet.payer
+    );
+    const account = await client.getAccountInfo(token.publicKey);
+    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));
+  });
+
+  it("Can create a random token with prefunding under the rent exemption", async () => {
+    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],
+      instructions: [
+        anchor.web3.SystemProgram.transfer({
+          fromPubkey: program.provider.wallet.publicKey,
+          toPubkey: token.publicKey,
+          lamports: 1,
+        }),
+      ],
+    });
+    const client = new Token(
+      program.provider.connection,
+      mint.publicKey,
+      TOKEN_PROGRAM_ID,
+      program.provider.wallet.payer
+    );
+    const account = await client.getAccountInfo(token.publicKey);
+    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));
+  });
 });

+ 142 - 102
lang/syn/src/codegen/accounts/constraints.rs

@@ -446,88 +446,66 @@ pub fn generate_pda(
     };
 
     match kind {
-        InitKind::Token { owner, mint } => quote! {
-            let #field: #combined_account_ty = {
-                #payer
-
-                // Fund the account for rent exemption.
-                let required_lamports = __anchor_rent
-                    .minimum_balance(anchor_spl::token::TokenAccount::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::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.to_account_info().clone(),
-                    ],
-                    &[#seeds_with_nonce],
-                )?;
+        InitKind::Token { owner, mint } => {
+            let create_account = generate_create_account(
+                field,
+                quote! {anchor_spl::token::TokenAccount::LEN},
+                quote! {token_program.to_account_info().key},
+                seeds_with_nonce,
+            );
+            quote! {
+                let #field: #combined_account_ty = {
+                    // Define payer variable.
+                    #payer
 
-                // 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(),
+                    // Create the account with the system program.
+                    #create_account
+
+                    // 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_unchecked(
+                        &#field.to_account_info(),
+                    )?
                 };
-                let cpi_ctx = CpiContext::new(cpi_program, accounts);
-                anchor_spl::token::initialize_account(cpi_ctx)?;
-                anchor_lang::CpiAccount::try_from_unchecked(
-                    &#field.to_account_info(),
-                )?
-            };
-        },
-        InitKind::Mint { owner, decimals } => quote! {
-            let #field: #combined_account_ty = {
-                #payer
-
-                // 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::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().clone(),
-                    ],
-                    &[#seeds_with_nonce],
-                )?;
+            }
+        }
+        InitKind::Mint { owner, decimals } => {
+            let create_account = generate_create_account(
+                field,
+                quote! {anchor_spl::token::Mint::LEN},
+                quote! {token_program.to_account_info().key},
+                seeds_with_nonce,
+            );
+            quote! {
+                let #field: #combined_account_ty = {
+                    // Define payer variable.
+                    #payer
 
-                // Initialize the mint account.
-                let cpi_program = token_program.to_account_info();
-                let accounts = anchor_spl::token::InitializeMint {
-                    mint: #field.to_account_info(),
-                    rent: rent.to_account_info(),
+                    // Create the account with the system program.
+                    #create_account
+
+                    // Initialize the mint account.
+                    let cpi_program = token_program.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_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, None)?;
+                    anchor_lang::CpiAccount::try_from_unchecked(
+                        &#field.to_account_info(),
+                    )?
                 };
-                let cpi_ctx = CpiContext::new(cpi_program, accounts);
-                anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, None)?;
-                anchor_lang::CpiAccount::try_from_unchecked(
-                    &#field.to_account_info(),
-                )?
-            };
-        },
+            }
+        }
         InitKind::Program { owner } => {
             let space = match space {
                 // If no explicit space param was given, serialize the type to bytes
@@ -560,34 +538,13 @@ pub fn generate_pda(
                     &#o
                 },
             };
+            let create_account =
+                generate_create_account(field, quote! {space}, owner, seeds_with_nonce);
             quote! {
                 let #field = {
                     #space
                     #payer
-
-                    let lamports = __anchor_rent.minimum_balance(space);
-                    let ix = anchor_lang::solana_program::system_instruction::create_account(
-                        payer.to_account_info().key,
-                        #field.to_account_info().key,
-                        lamports,
-                        space as u64,
-                        #owner,
-                    );
-
-                    anchor_lang::solana_program::program::invoke_signed(
-                        &ix,
-                        &[
-
-                            #field.to_account_info(),
-                            payer.to_account_info(),
-                            system_program.to_account_info(),
-                        ],
-                        &[#seeds_with_nonce],
-                    ).map_err(|e| {
-                        anchor_lang::solana_program::msg!("Unable to create associated account");
-                        e
-                    })?;
-
+                    #create_account
                     let mut pa: #combined_account_ty = #try_from;
                     pa
                 };
@@ -596,6 +553,89 @@ pub fn generate_pda(
     }
 }
 
+// Generated code to create an account with with system program with the
+// given `space` amount of data, owned by `owner`.
+//
+// `seeds_with_nonce` should be given for creating PDAs. Otherwise it's an
+// empty stream.
+pub fn generate_create_account(
+    field: &Ident,
+    space: proc_macro2::TokenStream,
+    owner: proc_macro2::TokenStream,
+    seeds_with_nonce: proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+    quote! {
+        // If the account being initialized already has lamports, then
+        // return them all back to the payer so that the account has
+        // zero lamports when the system program's create instruction
+        // is eventually called.
+        let __current_lamports = #field.to_account_info().lamports();
+        if __current_lamports == 0 {
+            // Create the token account with right amount of lamports and space, and the correct owner.
+            let lamports = __anchor_rent.minimum_balance(#space);
+            anchor_lang::solana_program::program::invoke_signed(
+                &anchor_lang::solana_program::system_instruction::create_account(
+                    payer.to_account_info().key,
+                    #field.to_account_info().key,
+                    lamports,
+                    #space as u64,
+                    #owner,
+                ),
+                &[
+                    payer.to_account_info(),
+                    #field.to_account_info(),
+                    system_program.to_account_info().clone(),
+                ],
+                &[#seeds_with_nonce],
+            )?;
+        } else {
+            // Fund the account for rent exemption.
+            let required_lamports = __anchor_rent
+                .minimum_balance(#space)
+                .max(1)
+                .saturating_sub(__current_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.
+            anchor_lang::solana_program::program::invoke_signed(
+                &anchor_lang::solana_program::system_instruction::allocate(
+                    #field.to_account_info().key,
+                    #space as u64,
+                ),
+                &[
+                    #field.to_account_info(),
+                    system_program.clone(),
+                ],
+                &[#seeds_with_nonce],
+            )?;
+            // Assign to the spl token program.
+            anchor_lang::solana_program::program::invoke_signed(
+                &anchor_lang::solana_program::system_instruction::assign(
+                    #field.to_account_info().key,
+                    #owner,
+                ),
+                &[
+                    #field.to_account_info(),
+                    system_program.to_account_info(),
+                ],
+                &[#seeds_with_nonce],
+            )?;
+        }
+    }
+}
+
 pub fn generate_constraint_executable(
     f: &Field,
     _c: &ConstraintExecutable,

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

@@ -436,6 +436,8 @@ pub struct ConstraintSpace {
 #[allow(clippy::large_enum_variant)]
 pub enum InitKind {
     Program { owner: Option<Expr> },
+    // Owner for token and mint represents the authority. Not to be confused
+    // with the owner of the AccountInfo.
     Token { owner: Expr, mint: Expr },
     Mint { owner: Expr, decimals: Expr },
 }