use ts builder api (#54)
Co-authored-by: Paul Schaaf <paulsimonschaaf@gmail.com>
This commit is contained in:
parent
0099a11a65
commit
e6603e3e2a
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@project-serum/anchor": "^0.23.0"
|
"@project-serum/anchor": "0.23.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crate::errors::TicTacToeError;
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use num_derive::*;
|
use num_derive::*;
|
||||||
use num_traits::*;
|
use num_traits::*;
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
|
|
|
@ -6,14 +6,15 @@ import chaiAsPromised from 'chai-as-promised';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
chai.use(chaiAsPromised);
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
async function play(program, game, player, tile, expectedTurn, expectedGameState, expectedBoard) {
|
async function play(program: Program<TicTacToe>, game, player, tile, expectedTurn, expectedGameState, expectedBoard) {
|
||||||
await program.rpc.play(tile, {
|
await program.methods
|
||||||
accounts: {
|
.play(tile)
|
||||||
|
.accounts({
|
||||||
player: player.publicKey,
|
player: player.publicKey,
|
||||||
game
|
game
|
||||||
},
|
})
|
||||||
signers: player instanceof (anchor.Wallet as any) ? [] : [player]
|
.signers(player instanceof (anchor.Wallet as any) ? [] : [player])
|
||||||
});
|
.rpc();
|
||||||
|
|
||||||
const gameState = await program.account.game.fetch(game);
|
const gameState = await program.account.game.fetch(game);
|
||||||
expect(gameState.turn).to.equal(expectedTurn);
|
expect(gameState.turn).to.equal(expectedTurn);
|
||||||
|
@ -33,14 +34,14 @@ describe('tic-tac-toe', () => {
|
||||||
const gameKeypair = anchor.web3.Keypair.generate();
|
const gameKeypair = anchor.web3.Keypair.generate();
|
||||||
const playerOne = program.provider.wallet;
|
const playerOne = program.provider.wallet;
|
||||||
const playerTwo = anchor.web3.Keypair.generate();
|
const playerTwo = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.setupGame(playerTwo.publicKey, {
|
await program.methods
|
||||||
accounts: {
|
.setupGame(playerTwo.publicKey)
|
||||||
|
.accounts({
|
||||||
game: gameKeypair.publicKey,
|
game: gameKeypair.publicKey,
|
||||||
playerOne: playerOne.publicKey,
|
playerOne: playerOne.publicKey,
|
||||||
systemProgram: anchor.web3.SystemProgram.programId
|
})
|
||||||
},
|
.signers([gameKeypair])
|
||||||
signers: [gameKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
||||||
expect(gameState.turn).to.equal(1);
|
expect(gameState.turn).to.equal(1);
|
||||||
|
@ -57,14 +58,14 @@ describe('tic-tac-toe', () => {
|
||||||
const gameKeypair = anchor.web3.Keypair.generate();
|
const gameKeypair = anchor.web3.Keypair.generate();
|
||||||
const playerOne = program.provider.wallet;
|
const playerOne = program.provider.wallet;
|
||||||
const playerTwo = anchor.web3.Keypair.generate();
|
const playerTwo = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.setupGame(playerTwo.publicKey, {
|
await program.methods
|
||||||
accounts: {
|
.setupGame(playerTwo.publicKey)
|
||||||
|
.accounts({
|
||||||
game: gameKeypair.publicKey,
|
game: gameKeypair.publicKey,
|
||||||
playerOne: playerOne.publicKey,
|
playerOne: playerOne.publicKey,
|
||||||
systemProgram: anchor.web3.SystemProgram.programId
|
})
|
||||||
},
|
.signers([gameKeypair])
|
||||||
signers: [gameKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
||||||
expect(gameState.turn).to.equal(1);
|
expect(gameState.turn).to.equal(1);
|
||||||
|
@ -243,14 +244,14 @@ describe('tic-tac-toe', () => {
|
||||||
const gameKeypair = anchor.web3.Keypair.generate();
|
const gameKeypair = anchor.web3.Keypair.generate();
|
||||||
const playerOne = program.provider.wallet;
|
const playerOne = program.provider.wallet;
|
||||||
const playerTwo = anchor.web3.Keypair.generate();
|
const playerTwo = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.setupGame(playerTwo.publicKey, {
|
await program.methods
|
||||||
accounts: {
|
.setupGame(playerTwo.publicKey)
|
||||||
|
.accounts({
|
||||||
game: gameKeypair.publicKey,
|
game: gameKeypair.publicKey,
|
||||||
playerOne: playerOne.publicKey,
|
playerOne: playerOne.publicKey,
|
||||||
systemProgram: anchor.web3.SystemProgram.programId
|
})
|
||||||
},
|
.signers([gameKeypair])
|
||||||
signers: [gameKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
||||||
expect(gameState.turn).to.equal(1);
|
expect(gameState.turn).to.equal(1);
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
"@ethersproject/logger" "^5.5.0"
|
"@ethersproject/logger" "^5.5.0"
|
||||||
hash.js "1.1.7"
|
hash.js "1.1.7"
|
||||||
|
|
||||||
"@project-serum/anchor@^0.23.0":
|
"@project-serum/anchor@0.23.0":
|
||||||
version "0.23.0"
|
version "0.23.0"
|
||||||
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.23.0.tgz#2b2eb6b51601b073e8db26663aa2d6c2f2841771"
|
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.23.0.tgz#2b2eb6b51601b073e8db26663aa2d6c2f2841771"
|
||||||
integrity sha512-LV2/ifZOJVFTZ4GbEloXln3iVfCvO1YM8i7BBCrUm4tehP7irMx4nr4/IabHWOzrQcQElsxSP/lb1tBp+2ff8A==
|
integrity sha512-LV2/ifZOJVFTZ4GbEloXln3iVfCvO1YM8i7BBCrUm4tehP7irMx4nr4/IabHWOzrQcQElsxSP/lb1tBp+2ff8A==
|
||||||
|
|
|
@ -152,21 +152,22 @@ describe('puppet', () => {
|
||||||
const puppetKeypair = Keypair.generate();
|
const puppetKeypair = Keypair.generate();
|
||||||
|
|
||||||
it('Does CPI!', async () => {
|
it('Does CPI!', async () => {
|
||||||
await puppetProgram.rpc.initialize({
|
await puppetProgram.methods
|
||||||
accounts: {
|
.initialize()
|
||||||
puppet: puppetKeypair.publicKey,
|
.accounts({
|
||||||
user: anchor.getProvider().wallet.publicKey,
|
puppet: puppetKeypair.publicKey,
|
||||||
systemProgram: SystemProgram.programId
|
user: anchor.getProvider().wallet.publicKey,
|
||||||
},
|
})
|
||||||
signers: [puppetKeypair]
|
.signers([puppetKeypair])
|
||||||
});
|
.rpc();
|
||||||
|
|
||||||
await puppetMasterProgram.rpc.pullStrings(new anchor.BN(42),{
|
await puppetMasterProgram.methods
|
||||||
accounts: {
|
.pullStrings(new anchor.BN(42))
|
||||||
puppetProgram: puppetProgram.programId,
|
.accounts({
|
||||||
puppet: puppetKeypair.publicKey
|
puppetProgram: puppetProgram.programId,
|
||||||
}
|
puppet: puppetKeypair.publicKey
|
||||||
})
|
})
|
||||||
|
.rpc();
|
||||||
|
|
||||||
expect((await puppetProgram.account.data
|
expect((await puppetProgram.account.data
|
||||||
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
||||||
|
@ -281,23 +282,24 @@ describe('puppet', () => {
|
||||||
const authorityKeypair = Keypair.generate();
|
const authorityKeypair = Keypair.generate();
|
||||||
|
|
||||||
it('Does CPI!', async () => {
|
it('Does CPI!', async () => {
|
||||||
await puppetProgram.rpc.initialize(authorityKeypair.publicKey, {
|
await puppetProgram.methods
|
||||||
accounts: {
|
.initialize(authorityKeypair.publicKey)
|
||||||
puppet: puppetKeypair.publicKey,
|
.accounts({
|
||||||
user: anchor.getProvider().wallet.publicKey,
|
puppet: puppetKeypair.publicKey,
|
||||||
systemProgram: SystemProgram.programId,
|
user: anchor.getProvider().wallet.publicKey,
|
||||||
},
|
})
|
||||||
signers: [puppetKeypair]
|
.signers([puppetKeypair])
|
||||||
});
|
.rpc();
|
||||||
|
|
||||||
await puppetMasterProgram.rpc.pullStrings(new anchor.BN(42),{
|
await puppetMasterProgram.methods
|
||||||
accounts: {
|
.pullStrings(new anchor.BN(42))
|
||||||
puppetProgram: puppetProgram.programId,
|
.accounts({
|
||||||
puppet: puppetKeypair.publicKey,
|
puppetProgram: puppetProgram.programId,
|
||||||
authority: authorityKeypair.publicKey
|
puppet: puppetKeypair.publicKey,
|
||||||
},
|
authority: authorityKeypair.publicKey
|
||||||
signers: [authorityKeypair]
|
})
|
||||||
})
|
.signers([authorityKeypair])
|
||||||
|
.rpc();
|
||||||
|
|
||||||
expect((await puppetProgram.account.data
|
expect((await puppetProgram.account.data
|
||||||
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
||||||
|
|
|
@ -198,22 +198,23 @@ anchor.setProvider(anchor.Provider.env());
|
||||||
program.programId
|
program.programId
|
||||||
);
|
);
|
||||||
|
|
||||||
await program.rpc.createUserStats("brian", {
|
await program.methods
|
||||||
accounts: {
|
.createUserStats("brian")
|
||||||
|
.accounts({
|
||||||
user: anchor.getProvider().wallet.publicKey,
|
user: anchor.getProvider().wallet.publicKey,
|
||||||
userStats: userStatsPDA,
|
userStats: userStatsPDA,
|
||||||
systemProgram: SystemProgram.programId
|
})
|
||||||
}
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal("brian");
|
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal("brian");
|
||||||
|
|
||||||
await program.rpc.changeUserName("tom", {
|
await program.methods
|
||||||
accounts: {
|
.changeUserName("tom")
|
||||||
|
.accounts({
|
||||||
user: anchor.getProvider().wallet.publicKey,
|
user: anchor.getProvider().wallet.publicKey,
|
||||||
userStats: userStatsPDA
|
userStats: userStatsPDA
|
||||||
}
|
})
|
||||||
})
|
.rpc();
|
||||||
|
|
||||||
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal("tom");
|
expect((await program.account.userStats.fetch(userStatsPDA)).name).to.equal("tom");
|
||||||
});
|
});
|
||||||
|
@ -305,22 +306,23 @@ describe('puppet', () => {
|
||||||
const [puppetMasterPDA, puppetMasterBump] = await PublicKey
|
const [puppetMasterPDA, puppetMasterBump] = await PublicKey
|
||||||
.findProgramAddress([], puppetMasterProgram.programId);
|
.findProgramAddress([], puppetMasterProgram.programId);
|
||||||
|
|
||||||
await puppetProgram.rpc.initialize(puppetMasterPDA, {
|
await puppetProgram.methods
|
||||||
accounts: {
|
.initialize(puppetMasterPDA)
|
||||||
|
.accounts({
|
||||||
puppet: puppetKeypair.publicKey,
|
puppet: puppetKeypair.publicKey,
|
||||||
user: anchor.getProvider().wallet.publicKey,
|
user: anchor.getProvider().wallet.publicKey,
|
||||||
systemProgram: SystemProgram.programId,
|
})
|
||||||
},
|
.signers([puppetKeypair])
|
||||||
signers: [puppetKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
await puppetMasterProgram.rpc.pullStrings(puppetMasterBump, new anchor.BN(42),{
|
await puppetMasterProgram.methods
|
||||||
accounts: {
|
.pullStrings(puppetMasterBump, new anchor.BN(42))
|
||||||
|
.accounts({
|
||||||
puppetProgram: puppetProgram.programId,
|
puppetProgram: puppetProgram.programId,
|
||||||
puppet: puppetKeypair.publicKey,
|
puppet: puppetKeypair.publicKey,
|
||||||
authority: puppetMasterPDA
|
authority: puppetMasterPDA
|
||||||
,
|
})
|
||||||
}});
|
.rpc();
|
||||||
|
|
||||||
expect((await puppetProgram.account.data
|
expect((await puppetProgram.account.data
|
||||||
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
.fetch(puppetKeypair.publicKey)).data.toNumber()).to.equal(42);
|
||||||
|
|
|
@ -280,14 +280,14 @@ Time to test our code! Head over into the `tests` folder in the root directory.
|
||||||
const gameKeypair = anchor.web3.Keypair.generate();
|
const gameKeypair = anchor.web3.Keypair.generate();
|
||||||
const playerOne = program.provider.wallet;
|
const playerOne = program.provider.wallet;
|
||||||
const playerTwo = anchor.web3.Keypair.generate();
|
const playerTwo = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.setupGame(playerTwo.publicKey, {
|
await program.methods
|
||||||
accounts: {
|
.setupGame(playerTwo.publicKey)
|
||||||
|
.accounts({
|
||||||
game: gameKeypair.publicKey,
|
game: gameKeypair.publicKey,
|
||||||
playerOne: playerOne.publicKey,
|
playerOne: playerOne.publicKey,
|
||||||
systemProgram: anchor.web3.SystemProgram.programId
|
})
|
||||||
},
|
.signers([gameKeypair])
|
||||||
signers: [gameKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
||||||
expect(gameState.turn).to.equal(1);
|
expect(gameState.turn).to.equal(1);
|
||||||
|
@ -310,14 +310,15 @@ import { expect } from 'chai';
|
||||||
> This is likely because the test file is looking for types from your program that haven't been generated yet.
|
> This is likely because the test file is looking for types from your program that haven't been generated yet.
|
||||||
> To generate them, run `anchor build`. This builds the program and creates the idl and typescript types.
|
> To generate them, run `anchor build`. This builds the program and creates the idl and typescript types.
|
||||||
|
|
||||||
The test begins by creating some keypairs. Importantly, `playerOne` is not a keypair but the wallet of the program's provider. The provider details are defined in the `Anchor.toml` file in the root of the project.
|
The test begins by creating some keypairs. Importantly, `playerOne` is not a keypair but the wallet of the program's provider. The provider details are defined in the `Anchor.toml` file in the root of the project. The provider serves as the keypair that pays for (and therefore signs) all transactions.
|
||||||
Then, we send the transaction. Because the anchor typescript client has parsed the IDL, all transaction inputs have types. If you remove one of the accounts for example, typescript will complain.
|
Then, we send the transaction.
|
||||||
The structure of the transaction function is as follows: First come the instruction arguments. For this function, the public key of the second player. Then come the accounts. Lastly, we add a signers array. We have to add the `gameKeypair` here because whenever an account gets created, it has to sign its creation transaction. We don't have to add `playerOne` even though we gave it the `Signer` type in the program because it is the program provider and therefore signs the transaction by default.
|
The structure of the transaction function is as follows: First come the instruction arguments. For this function, the public key of the second player. Then come the accounts. Lastly, we add a signers array. We have to add the `gameKeypair` here because whenever an account gets created, it has to sign its creation transaction. We don't have to add `playerOne` even though we gave it the `Signer` type in the program because it is the program provider and therefore signs the transaction by default.
|
||||||
|
We did not have to specify the `system_program` account. This is because anchor recognizes this account and is able to infer it. This is also true for other known accounts such as the `token_program` or the `rent` sysvar account.
|
||||||
|
|
||||||
After the transaction returns, we can fetch the state of the game account. You can fetch account state using the `program.account` namespace.
|
After the transaction returns, we can fetch the state of the game account. You can fetch account state using the `program.account` namespace.
|
||||||
Finally, we verify the game has been set up properly. Anchor's typescript client deserializes rust enums like this: `{ active: {}}` for a fieldless variant and `{ won: { winner: Pubkey }}` for a variant with fields. The `None` variant of `Option` becomes `null`. The `Some(x)` variant becomes whatever `x` deserializes to.
|
Finally, we verify the game has been set up properly. Anchor's typescript client deserializes rust enums like this: `{ active: {}}` for a fieldless variant and `{ won: { winner: Pubkey }}` for a variant with fields. The `None` variant of `Option` becomes `null`. The `Some(x)` variant becomes whatever `x` deserializes to.
|
||||||
|
|
||||||
Now, run `anchor test`. This starts up (and subsequently shuts down) a local validator (make sure you don't have one running) and runs your tests using the test script defined in `Anchor.toml`.
|
Now, run `anchor test`. This starts up (and subsequently shuts down) a local validator (make sure you don't have one running before) and runs your tests using the test script defined in `Anchor.toml`.
|
||||||
|
|
||||||
> If you get the error `Error: Unable to read keypair file` when running the test, you likely need to generate a Solana keypair using `solana-keygen new`.
|
> If you get the error `Error: Unable to read keypair file` when running the test, you likely need to generate a Solana keypair using `solana-keygen new`.
|
||||||
|
|
||||||
|
@ -358,15 +359,16 @@ We've checked in the accounts struct that the `player` account has signed the tr
|
||||||
|
|
||||||
Testing the `play` instruction works the exact same way. To avoid repeating yourself, create a helper function at the top of the test file:
|
Testing the `play` instruction works the exact same way. To avoid repeating yourself, create a helper function at the top of the test file:
|
||||||
```typescript
|
```typescript
|
||||||
async function play(program, game, player,
|
async function play(program: Program<TicTacToe>, game, player,
|
||||||
tile, expectedTurn, expectedGameState, expectedBoard) {
|
tile, expectedTurn, expectedGameState, expectedBoard) {
|
||||||
await program.rpc.play(tile, {
|
await program.methods
|
||||||
accounts: {
|
.play(tile)
|
||||||
|
.accounts({
|
||||||
player: player.publicKey,
|
player: player.publicKey,
|
||||||
game
|
game
|
||||||
},
|
})
|
||||||
signers: player instanceof (anchor.Wallet as any) ? [] : [player]
|
.signers(player instanceof (anchor.Wallet as any) ? [] : [player])
|
||||||
});
|
.rpc();
|
||||||
|
|
||||||
const gameState = await program.account.game.fetch(game);
|
const gameState = await program.account.game.fetch(game);
|
||||||
expect(gameState.turn).to.equal(expectedTurn);
|
expect(gameState.turn).to.equal(expectedTurn);
|
||||||
|
@ -383,14 +385,14 @@ it('player one wins', async() => {
|
||||||
const gameKeypair = anchor.web3.Keypair.generate();
|
const gameKeypair = anchor.web3.Keypair.generate();
|
||||||
const playerOne = program.provider.wallet;
|
const playerOne = program.provider.wallet;
|
||||||
const playerTwo = anchor.web3.Keypair.generate();
|
const playerTwo = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.setupGame(playerTwo.publicKey, {
|
await program.methods
|
||||||
accounts: {
|
.setupGame(playerTwo.publicKey)
|
||||||
|
.accounts({
|
||||||
game: gameKeypair.publicKey,
|
game: gameKeypair.publicKey,
|
||||||
playerOne: playerOne.publicKey,
|
playerOne: playerOne.publicKey,
|
||||||
systemProgram: anchor.web3.SystemProgram.programId
|
})
|
||||||
},
|
.signers([gameKeypair])
|
||||||
signers: [gameKeypair]
|
.rpc();
|
||||||
});
|
|
||||||
|
|
||||||
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
let gameState = await program.account.game.fetch(gameKeypair.publicKey);
|
||||||
expect(gameState.turn).to.equal(1);
|
expect(gameState.turn).to.equal(1);
|
||||||
|
|
Loading…
Reference in New Issue