diff --git a/project-2-voting/anchor/programs/voting/src/lib.rs b/project-2-voting/anchor/programs/voting/src/lib.rs index 396c9bd..53b9101 100644 --- a/project-2-voting/anchor/programs/voting/src/lib.rs +++ b/project-2-voting/anchor/programs/voting/src/lib.rs @@ -16,6 +16,7 @@ pub mod voting { ctx.accounts.poll_account.poll_description = description; ctx.accounts.poll_account.poll_voting_start = start_time; ctx.accounts.poll_account.poll_voting_end = end_time; + msg!("init poll called"); Ok(()) } @@ -24,10 +25,17 @@ pub mod voting { candidate: String) -> Result<()> { ctx.accounts.candidate_account.candidate_name = candidate; ctx.accounts.poll_account.poll_option_index += 1; + msg!("init candidate called"); Ok(()) } pub fn vote(ctx: Context, _poll_id: u64, _candidate: String) -> Result<()> { + /* + Please note here we can get Candidate mutable reference, it only indicates + that we can modify it in-memory, not indicating the data on solana chain + has been changed. If write permission is not given on `Vote` metadata, + then solana will silently ignore the change without modifying it. + */ let candidate_account = &mut ctx.accounts.candidate_account; let current_time = Clock::get()?.unix_timestamp; @@ -70,6 +78,8 @@ pub struct InitializeCandidate<'info> { #[account(mut)] pub signer: Signer<'info>, + // poll_option_index is changed, so need set mutable + #[account(mut)] pub poll_account: Account<'info, PollAccount>, #[account( @@ -98,10 +108,11 @@ pub struct Vote<'info> { pub poll_account: Account<'info, PollAccount>, #[account( - mut, + mut, // Vote() will modify a given candidate seeds = [poll_id.to_le_bytes().as_ref(), candidate.as_ref()], bump)] pub candidate_account: Account<'info, CandidateAccount>, + // no system_account because no creating account in Vote() } #[account] diff --git a/project-2-voting/anchor/tests/bankrun.spec.ts b/project-2-voting/anchor/tests/bankrun.spec.ts index 033c91f..4faa367 100644 --- a/project-2-voting/anchor/tests/bankrun.spec.ts +++ b/project-2-voting/anchor/tests/bankrun.spec.ts @@ -4,6 +4,9 @@ import { PublicKey } from '@solana/web3.js'; import * as anchor from '@coral-xyz/anchor'; import { BN, Program } from "@coral-xyz/anchor"; +// This is a "Bankrun" way of writing cases. Bankrun helps manage all the setup stuff +// needed for case to run successfully. It doesn't depend on solana-test-validator, so +// it's more convenient way to do testing. const IDL = require("../target/idl/voting.json"); import { Voting } from '../target/types/voting'; @@ -12,21 +15,25 @@ const PUPPET_PROGRAM_ID = new PublicKey("5s3PtT8kLYCv1WEp6dSh3T7EuF35Z6jSu5Cvx4h describe('Create a system account', () => { - test("bankrun", async () => { - const context = await startAnchor("", [{name: "voting", programId: PUPPET_PROGRAM_ID}], []); - const provider = new BankrunProvider(context); + let context; + let provider; + let votingProg; + let pollAddress; - const puppetProgram = new Program( - IDL, - provider, - ); - - const [pollAddress] = PublicKey.findProgramAddressSync( + beforeAll(async () => { + console.log("init context,provider,voting program instance ..."); + context = await startAnchor("", [{name: "voting", programId: PUPPET_PROGRAM_ID}], []); + provider = new BankrunProvider(context); + votingProg = new Program(IDL, provider); + [pollAddress] = PublicKey.findProgramAddressSync( [Buffer.from("poll"), new anchor.BN(1).toArrayLike(Buffer, "le", 8)], - puppetProgram.programId + votingProg.programId ); + console.log("program id: ", votingProg.programId); + }) - await puppetProgram.methods.initializePoll( + test("initialize poll", async () => { + await votingProg.methods.initializePoll( new anchor.BN(1), new anchor.BN(0), new anchor.BN(1759508293), @@ -34,9 +41,55 @@ describe('Create a system account', () => { "description", ).rpc(); - const pollAccount = await puppetProgram.account.pollAccount.fetch(pollAddress); + const pollAccount = await votingProg.account.pollAccount.fetch(pollAddress); console.log(pollAccount); + expect(pollAccount.pollOptionIndex.toNumber()).toEqual(0); + expect(pollAccount.pollDescription).toEqual("description"); + expect(pollAccount.pollVotingStart.toNumber()) + .toBeLessThan(pollAccount.pollVotingEnd.toNumber()); + }); + + it("initialize candidate", async() => { + await votingProg.methods.initializeCandidate( + new anchor.BN(1), + "Smooth" + ).accounts({pollAccount: pollAddress}) + .rpc(); + + await votingProg.methods.initializeCandidate( + new anchor.BN(1), + "Crunchy" + ).accounts({pollAccount: pollAddress}) + .rpc(); + + const [crunchyAddr] = PublicKey.findProgramAddressSync( + [new anchor.BN(1).toArrayLike(Buffer, 'le', 8), Buffer.from("Crunchy")], + votingProg.programId, + ); + const crunchyData = await votingProg.account.candidateAccount.fetch(crunchyAddr); + console.log(crunchyData); + expect(crunchyData.candidateVotes.toNumber()).toEqual(0); + const pollData = await votingProg.account.pollAccount.fetch(pollAddress); + expect(pollData.pollOptionIndex.toNumber()).toEqual(2); + }); + + it("vote", async() => { + await votingProg.methods.vote( + new anchor.BN(1), + "Crunchy" + ).rpc(); + + await votingProg.methods.vote( + new anchor.BN(1), + "Crunchy" + ).rpc(); + const [crunchyAddr] = PublicKey.findProgramAddressSync( + [new anchor.BN(1).toArrayLike(Buffer, 'le', 8), Buffer.from("Crunchy")], + votingProg.programId, + ); + const crunchyData = await votingProg.account.candidateAccount.fetch(crunchyAddr); + expect(crunchyData.candidateVotes.toNumber()).toEqual(2); }); }); \ No newline at end of file diff --git a/project-2-voting/anchor/tests/basic.spec.ts b/project-2-voting/anchor/tests/basic.spec.ts index f69a29e..ce87092 100644 --- a/project-2-voting/anchor/tests/basic.spec.ts +++ b/project-2-voting/anchor/tests/basic.spec.ts @@ -3,11 +3,16 @@ import { Program } from '@coral-xyz/anchor'; import { Voting } from '../target/types/voting'; import { PublicKey } from '@solana/web3.js'; +// This is "non-Bankrun" way of writing cases.In this case you must use `solana-test-validator` +// automatically(`anchor test` would do that) or +// manually(run `solana-test-validator` in separate terminal) + describe('Voting', () => { // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); const program = anchor.workspace.Voting as Program; + console.log("program id: ", program.programId); it('initializePoll', async () => { @@ -15,17 +20,21 @@ describe('Voting', () => { [Buffer.from("poll"), new anchor.BN(1).toArrayLike(Buffer, "le", 8)], program.programId ); + // better to wrap program's instruction calling in case that program doesn't + // exists in chain and the call just abandons the error + try { + const tx = await program.methods.initializePoll( + new anchor.BN(1), + new anchor.BN(0), + new anchor.BN(1759508293), + "test-poll", + "description", + ).rpc(); - const tx = await program.methods.initializePoll( - new anchor.BN(1), - new anchor.BN(0), - new anchor.BN(1759508293), - "test-poll", - "description", - ) - .rpc(); - - console.log('Your transaction signature', tx); + console.log('Your transaction signature', tx); + } catch (e) { + console.error('initializePoll failed:', e); + } }); it('initialize candidates', async () => {