Skip to content

Commit

Permalink
anchor: create share ATA when it doesn't exist (#72)
Browse files Browse the repository at this point in the history
1. use anchor's init-if-needed feature to automatically create share ATA
when the account doesn't exist
2. increase CU limit for subscribe function
  • Loading branch information
yurushao authored Apr 7, 2024
1 parent 374f8fe commit cca34a2
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 191 deletions.
14 changes: 12 additions & 2 deletions anchor/programs/glam/src/instructions/investor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anchor_lang::prelude::*;

use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::Token;
use anchor_spl::token_interface::{
burn, mint_to, transfer_checked, Burn, Mint, MintTo, Token2022, TokenAccount, TransferChecked,
Expand All @@ -21,12 +22,18 @@ fn log_price(price: Price) -> f64 {

#[derive(Accounts)]
pub struct Subscribe<'info> {
pub fund: Account<'info, Fund>,
pub fund: Box<Account<'info, Fund>>,

// the shares to mint
#[account(mut, mint::authority = share_class, mint::token_program = token_2022_program)]
pub share_class: Box<InterfaceAccount<'info, Mint>>, // mint
#[account(mut)]
#[account(
init_if_needed,
payer = signer,
associated_token::mint = share_class,
associated_token::authority = signer,
associated_token::token_program = token_2022_program
)]
pub signer_share_ata: Box<InterfaceAccount<'info, TokenAccount>>, // user account

// the asset to transfer
Expand All @@ -42,6 +49,8 @@ pub struct Subscribe<'info> {
pub signer: Signer<'info>,

// programs
pub system_program: Program<'info, System>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Program<'info, Token>,
pub token_2022_program: Program<'info, Token2022>,
}
Expand All @@ -62,6 +71,7 @@ pub fn subscribe_handler<'c: 'info, 'info>(
let asset_info = ctx.accounts.asset.to_account_info();
let asset_key = asset_info.key();
let asset_idx = fund.assets.iter().position(|&asset| asset == asset_key);

require!(asset_idx.is_some(), InvestorError::InvalidAssetSubscribe);
let asset_idx = asset_idx.unwrap();
//TODO check if in_kind is allowed, or idx must be 0
Expand Down
10 changes: 10 additions & 0 deletions anchor/target/idl/glam.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@
"isMut": true,
"isSigner": true
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
},
{
"name": "associatedTokenProgram",
"isMut": false,
"isSigner": false
},
{
"name": "tokenProgram",
"isMut": false,
Expand Down
20 changes: 20 additions & 0 deletions anchor/target/types/glam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ export type Glam = {
"isMut": true,
"isSigner": true
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
},
{
"name": "associatedTokenProgram",
"isMut": false,
"isSigner": false
},
{
"name": "tokenProgram",
"isMut": false,
Expand Down Expand Up @@ -946,6 +956,16 @@ export const IDL: Glam = {
"isMut": true,
"isSigner": true
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
},
{
"name": "associatedTokenProgram",
"isMut": false,
"isSigner": false
},
{
"name": "tokenProgram",
"isMut": false,
Expand Down
147 changes: 25 additions & 122 deletions anchor/tests/glam_crud.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import {
Keypair,
PublicKey,
ComputeBudgetProgram,
ComputeBudgetInstruction
} from "@solana/web3.js";
import {
createMint,
createAssociatedTokenAccount,
getAssociatedTokenAddressSync,
mintTo,
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
TOKEN_2022_PROGRAM_ID
} from "@solana/spl-token";
import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import { Glam } from "../target/types/glam";

describe("glam_crud", () => {
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const connection = provider.connection;

const manager = provider.wallet as anchor.Wallet;
console.log("Manager:", manager.publicKey);
Expand Down Expand Up @@ -117,114 +103,31 @@ describe("glam_crud", () => {
expect(fund.isActive).toEqual(true);
});

// it("Update fund", async () => {
// const newFundName = "Updated fund name";
// await program.methods
// .update(newFundName, null, null, false)
// .accounts({
// fund: fundPDA,
// manager: manager.publicKey
// })
// .rpc({ commitment });
// const fund = await program.account.fund.fetch(fundPDA);
// expect(fund.name).toEqual(newFundName);
// expect(fund.isActive).toEqual(false);
// });

// it("Close fund", async () => {
// await program.methods
// .close()
// .accounts({
// fund: fundPDA,
// manager: manager.publicKey
// })
// .rpc();

// // The account should no longer exist, returning null.
// const closedAccount = await program.account.fund.fetchNullable(fundPDA);
// expect(closedAccount).toBeNull();
// });

/*
it('Before any fund - create test assets', async () => {
await Promise.all( // exec in parallel, but await before ending the test
tokenKeypairs.map(async (token) => {
const mint = await createMint(
provider.connection,
payer.payer,
payer.publicKey,
null,
6,
token,
{ commitment }, // await 'confirmed'
);
const payerATA = await createAssociatedTokenAccount(
provider.connection,
payer.payer,
token.publicKey,
payer.publicKey,
);
await mintTo(
provider.connection,
payer.payer,
token.publicKey,
payerATA,
payer.payer,
1000,
[],
{ commitment }, // await 'confirmed'
);
it("Update fund", async () => {
const newFundName = "Updated fund name";
await program.methods
.update(newFundName, null, null, false)
.accounts({
fund: fundPDA,
manager: manager.publicKey
})
);
.rpc({ commitment });
const fund = await program.account.fund.fetch(fundPDA);
expect(fund.name).toEqual(newFundName);
expect(fund.isActive).toEqual(false);
});
*/

// it('Create Drift trading account', async () => {
// const userAccountPublicKey = await getUserAccountPublicKey(
// DRIFT_PROGRAM_ID,
// treasuryPDA,
// 0
// );
// const userStatsAccountPublicKey = await getUserStatsAccountPublicKey(
// DRIFT_PROGRAM_ID,
// treasuryPDA
// );
// const statePublicKey = await getDriftStateAccountPublicKey(
// DRIFT_PROGRAM_ID,
// );

// console.log("userAccountPublicKey", userAccountPublicKey);
// console.log("userStatsAccountPublicKey", userStatsAccountPublicKey);
// console.log("statePublicKey", statePublicKey);
// console.log("fundPDA", fundPDA);
// console.log("treasuryPDA", treasuryPDA);

// try {
// const txId = await program.methods
// .driftInitialize()
// .accounts({
// fund: fundPDA,
// treasury: treasuryPDA,
// userStats: userStatsAccountPublicKey,
// user: userAccountPublicKey,
// state: statePublicKey,
// manager: manager.publicKey,
// driftProgram: DRIFT_PROGRAM_ID,
// })
// .rpc({commitment}); // await 'confirmed'

// await connection.getParsedTransaction(txId, {commitment});
// console.log("driftInitialize", txId);
// } catch(e) {
// console.error(e);
// throw e;
// }
it("Close fund", async () => {
await program.methods
.close()
.accounts({
fund: fundPDA,
manager: manager.publicKey
})
.rpc();

// // const fund = await program.account.fund.fetch(fundPDA);
// // console.log(fund);
// // expect(fund.shareClassesLen).toEqual(1);
// // expect(fund.assetsLen).toEqual(3);
// }, /* timeout */ 10_000);
// The account should no longer exist, returning null.
const closedAccount = await program.account.fund.fetchNullable(fundPDA);
expect(closedAccount).toBeNull();
});
});
60 changes: 10 additions & 50 deletions anchor/tests/glam_investor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,10 @@ describe("glam_investor", () => {
expect(fund.isActive).toEqual(true);
});

it("Create ATAs", async () => {
//TODO: remove creation of ATA
// currently we need to manually create the ATAs
it("Create treasury ATAs", async () => {
// TODO: can we automatically create treasury ATAs?
try {
const tx1 = new Transaction().add(
// Treasury
const tx = new Transaction().add(
createAssociatedTokenAccountInstruction(
manager.publicKey,
treasuryUsdcAta,
Expand All @@ -377,55 +375,11 @@ describe("glam_investor", () => {
ASSOCIATED_TOKEN_PROGRAM_ID
)
);
await sendAndConfirmTransaction(connection, tx1, [manager.payer], {
await sendAndConfirmTransaction(connection, tx, [manager.payer], {
skipPreflight: true,
commitment
});

const tx2 = new Transaction().add(
// Shares
createAssociatedTokenAccountInstruction(
manager.publicKey,
managerSharesAta,
manager.publicKey,
sharePDA,
TOKEN_2022_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
),
createAssociatedTokenAccountInstruction(
manager.publicKey,
aliceSharesAta,
alice.publicKey,
sharePDA,
TOKEN_2022_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
),
createAssociatedTokenAccountInstruction(
manager.publicKey,
bobSharesAta,
bob.publicKey,
sharePDA,
TOKEN_2022_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
),
createAssociatedTokenAccountInstruction(
manager.publicKey,
eveSharesAta,
eve.publicKey,
sharePDA,
TOKEN_2022_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
)
);

const txSig = await sendAndConfirmTransaction(
connection,
tx2,
[manager.payer],
{ skipPreflight: true, commitment }
);
} catch (e) {
// create ATAs
console.error(e);
throw e;
}
Expand All @@ -449,6 +403,9 @@ describe("glam_investor", () => {
token2022Program: TOKEN_2022_PROGRAM_ID
})
.remainingAccounts(remainingAccountsSubscribe)
.preInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 })
])
.rpc({ commitment });
console.log("subscribe eth:", txId);
} catch (e) {
Expand Down Expand Up @@ -762,6 +719,9 @@ describe("glam_investor", () => {
token2022Program: TOKEN_2022_PROGRAM_ID
})
.remainingAccounts(remainingAccountsSubscribe)
.preInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 })
])
.signers([alice])
.rpc({ commitment });
console.log("tx:", txId);
Expand Down
Loading

0 comments on commit cca34a2

Please sign in to comment.