Skip to content

Commit 1cf2be5

Browse files
author
Lewis
committed
project8: refactor test case using native anchor testing framework
since bankrun libraries are deprecated TAKEAWAYS: try messing around the code and see what errors are
1 parent d7043f4 commit 1cf2be5

File tree

5 files changed

+119
-82
lines changed

5 files changed

+119
-82
lines changed

project-8-token-vesting/anchor/Anchor.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ vesting = "GFdLg11UBR8ZeePW43ZyD1gY4z4UQ96LPa22YBgnn4z8"
1111
url = "https://api.apr.dev"
1212

1313
[provider]
14-
cluster = "Localnet"
14+
cluster = "localnet"
1515
wallet = "~/.config/solana/id.json"
1616

1717
[scripts]

project-8-token-vesting/anchor/package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
"name": "@token-vesting/anchor",
33
"version": "0.0.1",
44
"dependencies": {
5-
"@coral-xyz/anchor": "^0.30.1",
6-
"@solana/spl-token": "^0.4.8",
7-
"@solana/web3.js": "1.94.0",
8-
"anchor-bankrun": "^0.4.0",
9-
"solana-bankrun": "^0.2.0",
10-
"spl-token-bankrun": "0.2.5"
5+
"@coral-xyz/anchor": "^0.31.1",
6+
"@solana/spl-token": "^0.4.13",
7+
"@solana/web3.js": "1.98.2"
118
},
129
"main": "./index.cjs",
1310
"module": "./index.js",

project-8-token-vesting/anchor/programs/vesting/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ default = []
1717
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
1818

1919
[dependencies]
20-
anchor-lang = { version="0.30.1", features=["init-if-needed"] }
21-
anchor-spl = "0.30.1"
22-
solana-program = "1.18.17"
20+
anchor-lang = { version="0.31.1", features=["init-if-needed"] }
21+
anchor-spl = "0.31.1"

project-8-token-vesting/anchor/programs/vesting/src/lib.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ pub mod vesting {
8787
signer_seeds
8888
);
8989
let decimals = ctx.accounts.mint.decimals;
90+
// claimable_amount is i64
9091
token_interface::transfer_checked(cpi_context, claimable_amount as u64, decimals)?;
9192
employee_account.total_withdrawn += claimable_amount;
9293
Ok(())
@@ -115,6 +116,11 @@ pub struct CreateVestingAccount<'info> {
115116
seeds = [b"vesting_treasury", company_name.as_bytes()],
116117
bump
117118
)]
119+
// treasury_token_account 是公司金库的 TokenAccount,它是由合约(PDA)控制的,
120+
// 不是某个用户钱包的“关联账户”
121+
// 是程序自定义的账户,而不是 SPL Token Associated Token Program 自动推导出来的账户。
122+
// 金库账户不是“某个用户的钱包+mint”的标准组合,而是“公司+mint”的唯一账户,由合约自己管理,
123+
// 所以它不能用 associated_token::xxx 这样的约束
118124
pub treasury_token_account: InterfaceAccount<'info, TokenAccount>,
119125
pub token_program: Interface<'info, TokenInterface>,
120126
pub system_program: Program<'info, System>,
@@ -124,7 +130,7 @@ pub struct CreateVestingAccount<'info> {
124130
pub struct CreateEmployeeAccount<'info> {
125131
#[account(mut)]
126132
pub owner: Signer<'info>,
127-
pub beneficiary: SystemAccount<'info>,
133+
pub beneficiary: SystemAccount<'info>, //why SystemAccount
128134
#[account(has_one = owner)]
129135
pub vesting_account: Account<'info, VestingAccount>,
130136
#[account(
@@ -147,6 +153,8 @@ pub struct ClaimTokens<'info> {
147153
mut,
148154
seeds = [b"employee_vesting", beneficiary.key().as_ref(), vesting_account.key().as_ref()],
149155
bump = employee_account.bump,
156+
// 如果随便换名,则报:no field `beneficiary2` on type `anchor_lang::prelude::Account<'_, EmployeeAccount>`
157+
// 这样可以防止恶意用户伪造账户,确保账户之间的归属关系正确
150158
has_one = beneficiary,
151159
has_one = vesting_account
152160
)]
@@ -158,9 +166,12 @@ pub struct ClaimTokens<'info> {
158166
has_one = treasury_token_account,
159167
has_one = mint
160168
)]
169+
// 公司级别的 vesting 账户,记录公司、mint、金库等信息
161170
pub vesting_account: Account<'info, VestingAccount>,
171+
// 代币的 mint 信息(即是哪种 SPL Token)
162172
pub mint: InterfaceAccount<'info, Mint>,
163-
#[account(mut)]
173+
#[account(mut)] // will change the token account's balance
174+
// 公司金库的 token 账户,存放待发放的代币
164175
pub treasury_token_account: InterfaceAccount<'info, TokenAccount>,
165176
#[account(
166177
init_if_needed,
@@ -169,8 +180,13 @@ pub struct ClaimTokens<'info> {
169180
associated_token::authority = beneficiary,
170181
associated_token::token_program = token_program
171182
)]
183+
// 员工自己的 token 账户,用于接收领取到的代币。如果不存在会自动创建
172184
pub employee_token_account: InterfaceAccount<'info, TokenAccount>,
173185
pub token_program: Interface<'info, TokenInterface>,
186+
/*
187+
在 Anchor 里,如果你用 associated_token::mint = ... 和 associated_token::authority = ...
188+
这样的语法,Anchor 会自动帮你用这个程序去创建(如果不存在)或查找(如果已存在)这个账户
189+
*/
174190
pub associated_token_program: Program<'info, AssociatedToken>,
175191
pub system_program: Program<'info, System>,
176192
}
Lines changed: 95 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,52 @@
11
// No imports needed: web3, anchor, pg and more are globally available
22
import * as anchor from "@coral-xyz/anchor";
3-
import { BankrunProvider } from "anchor-bankrun";
4-
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
3+
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
54
import { BN, Program } from "@coral-xyz/anchor";
5+
import { createMint, getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
66

7-
import {
8-
startAnchor,
9-
Clock,
10-
BanksClient,
11-
ProgramTestContext,
12-
} from "solana-bankrun";
13-
14-
import { createMint, mintTo } from "spl-token-bankrun";
15-
import { PublicKey, Keypair } from "@solana/web3.js";
16-
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
7+
import { PublicKey, Keypair, Connection, LAMPORTS_PER_SOL } from "@solana/web3.js";
178

189
import IDL from "../target/idl/vesting.json";
1910
import { Vesting } from "../target/types/vesting";
2011
import { SYSTEM_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/native/system";
2112

13+
// Original method is using bankrun libraries, but since it has been deprecated,
14+
// I refactored using native anchor testing framework
15+
2216
describe("Vesting Smart Contract Tests", () => {
2317
const companyName = "Company";
2418
let beneficiary: Keypair;
2519
let vestingAccountKey: PublicKey;
2620
let treasuryTokenAccount: PublicKey;
2721
let employeeAccount: PublicKey;
28-
let provider: BankrunProvider;
22+
let provider: anchor.AnchorProvider;
2923
let program: Program<Vesting>;
30-
let banksClient: BanksClient;
3124
let employer: Keypair;
25+
let payer: Keypair;
3226
let mint: PublicKey;
33-
let beneficiaryProvider: BankrunProvider;
34-
let program2: Program<Vesting>;
35-
let context: ProgramTestContext;
27+
// let beneficiaryProvider: anchor.AnchorProvider;
28+
// let program2: Program<Vesting>;
29+
const MY_DECIMALS = 2;
3630

3731
beforeAll(async () => {
3832
beneficiary = new anchor.web3.Keypair();
3933

40-
// set up bankrun
41-
context = await startAnchor(
42-
"",
43-
[{ name: "vesting", programId: new PublicKey(IDL.address) }],
44-
[
45-
{
46-
address: beneficiary.publicKey,
47-
info: {
48-
lamports: 1_000_000_000,
49-
data: Buffer.alloc(0),
50-
owner: SYSTEM_PROGRAM_ID,
51-
executable: false,
52-
},
53-
},
54-
]
55-
);
56-
provider = new BankrunProvider(context);
57-
34+
provider = anchor.AnchorProvider.env();
35+
// rent is needed when claiming tokens for creating employee_token_acount
36+
await provider.connection.requestAirdrop(beneficiary.publicKey,
37+
1 * LAMPORTS_PER_SOL);
5838
anchor.setProvider(provider);
39+
program = anchor.workspace.Vesting as Program<Vesting>;
40+
console.log("program id:", program.programId.toString(),
41+
",beneficiary:", beneficiary.publicKey.toString());
5942

60-
program = new Program<Vesting>(IDL as Vesting, provider);
61-
62-
banksClient = context.banksClient;
63-
64-
employer = provider.wallet.payer;
43+
employer = (provider.wallet as anchor.Wallet).payer;
44+
payer = employer;
6545

6646
// Create a new mint
67-
// @ts-ignore
68-
mint = await createMint(banksClient, employer, employer.publicKey, null, 2);
69-
70-
// Generate a new keypair for the beneficiary
71-
beneficiaryProvider = new BankrunProvider(context);
72-
beneficiaryProvider.wallet = new NodeWallet(beneficiary);
73-
74-
program2 = new Program<Vesting>(IDL as Vesting, beneficiaryProvider);
47+
mint = await createMint(provider.connection, payer,
48+
payer.publicKey, null, MY_DECIMALS);
49+
console.log("Mint address:", mint.toBase58(), "payer:", payer.publicKey.toString());
7550

7651
// Derive PDAs
7752
[vestingAccountKey] = PublicKey.findProgramAddressSync(
@@ -118,13 +93,22 @@ describe("Vesting Smart Contract Tests", () => {
11893

11994
it("should fund the treasury token account", async () => {
12095
const amount = 10_000 * 10 ** 9;
96+
97+
// 创建 employer 的 ATA(关联账户)
98+
// const employerTokenAccount = await getOrCreateAssociatedTokenAccount(
99+
// provider.connection,
100+
// payer,
101+
// mint,
102+
// payer.publicKey
103+
// );
104+
105+
// 给 treasuryTokenAccount 铸币
121106
const mintTx = await mintTo(
122-
// @ts-ignores
123-
banksClient,
124-
employer,
107+
provider.connection,
108+
payer,
125109
mint,
126110
treasuryTokenAccount,
127-
employer,
111+
payer, // mint authority/signer
128112
amount
129113
);
130114

@@ -139,34 +123,75 @@ describe("Vesting Smart Contract Tests", () => {
139123
vestingAccount: vestingAccountKey,
140124
})
141125
.rpc({ commitment: "confirmed", skipPreflight: true });
126+
//skipPreflight: 适合你确信交易不会失败,或者预检经常报奇怪的错误但实际能成功时
142127

143128
console.log("Create Employee Account Transaction Signature:", tx2);
144129
console.log("Employee account", employeeAccount.toBase58());
145130
});
146131

147132
it("should claim tokens", async () => {
148-
await new Promise((resolve) => setTimeout(resolve, 1000));
149-
150-
const currentClock = await banksClient.getClock();
151-
context.setClock(
152-
new Clock(
153-
currentClock.slot,
154-
currentClock.epochStartTimestamp,
155-
currentClock.epoch,
156-
currentClock.leaderScheduleEpoch,
157-
1000n
158-
)
159-
);
160-
161-
console.log("Employee account", employeeAccount.toBase58());
162-
163-
const tx3 = await program2.methods
133+
//await new Promise((resolve) => setTimeout(resolve, 1000));
134+
135+
// author says: the signer for this instruction is goint to be the beneficiary and not the employer
136+
/*
137+
The following is what I found by trial and error:
138+
139+
1. If not perform beneficiary airdrop,report:(won't be printed without try/catch):
140+
Error: SendTransactionError: Simulation failed.
141+
Message: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1.
142+
Logs:
143+
[
144+
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1595 of 181545 compute units",
145+
"Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=",
146+
"Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
147+
"Program 11111111111111111111111111111111 invoke [3]",
148+
"Transfer: insufficient lamports 0, need 2039280",
149+
"Program 11111111111111111111111111111111 failed: custom program error: 0x1"
150+
151+
2. unknown signer: xxx When only commenting out the line
152+
// beneficiary: beneficiary.publicKey,
153+
154+
3. when commenting out the line,report:
155+
Missing signature for public key xxx(beneficiary's key)
156+
Solana 要求:只要合约指令声明了某个账户为 signer,交易就必须用对应私钥签名
157+
// .signers([beneficiary])
158+
159+
4. 除了tokenProgram, beneficiary,其他字段似乎都可注释掉,用例可过
160+
如果仅注释了tokenProgram,则会报错:
161+
Reached maximum depth for account resolution. Unresolved accounts: `employeeTokenAccount
162+
原因(copilot gives): 它是 SPL Token 程序的地址,不是 PDA,也不是可以自动推导的账户,
163+
但它的 presence 让 Anchor 能正确识别和推导后续的 token 相关账户(比如 employeeTokenAccount
164+
165+
5. 同时注释掉beneficiary和signers,报错:
166+
AnchorError caused by account: employee_account. Error Code:
167+
AccountNotInitialized. Error Number: 3012. Error Message:
168+
The program expected this account to be already initialize
169+
6. 既然第5点报employee_acount 未初使化,则尝试在同时注释它俩时,添加employeeAccount: 报:
170+
AnchorError caused by account: employee_account. Error Code: ConstraintSeeds.
171+
Error Number: 2006. Error Message: A seeds constraint was violated.
172+
Program log: Left:
173+
Program log: BJRNd9PYp6oJNkioxJAdoZgNgt4TnNqGbKd6KuQVw9TS
174+
Program log: Right:
175+
Program log: 6xyHAzfxnWrjqK7zXTrTc9uw5zMh47Yr4VLFqYAu6mP
176+
以上这个报错在 .anchor/ 中没找到相关日志,但错误描述是在anchor 源码中的
177+
*/
178+
try {
179+
const tx3 = await program.methods
164180
.claimTokens(companyName)
165181
.accounts({
182+
beneficiary: beneficiary.publicKey,
183+
//employeeAccount: employeeAccount, // pda 帐户可自动推导
184+
//vestingAccount: vestingAccountKey,
185+
//mint,
186+
//treasuryTokenAccount: treasuryTokenAccount,
187+
//employeeTokenAccount: employeeTokenAccountPda,
166188
tokenProgram: TOKEN_PROGRAM_ID,
189+
//associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
167190
})
191+
.signers([beneficiary])
168192
.rpc({ commitment: "confirmed" });
169-
170-
console.log("Claim Tokens transaction signature", tx3);
193+
} catch (err) {
194+
throw err;
195+
}
171196
});
172197
});

0 commit comments

Comments
 (0)