Skip to content

Commit db49ce2

Browse files
committed
counter: add mpl-stack example (solita needs fix)
1 parent 30da422 commit db49ce2

File tree

16 files changed

+594
-0
lines changed

16 files changed

+594
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @ts-check
2+
const path = require('path');
3+
const programDir = path.join(__dirname);
4+
const idlDir = path.join(__dirname, 'idl');
5+
const sdkDir = path.join(__dirname, 'ts', 'generated');
6+
const binaryInstallDir = path.join(__dirname, 'target', 'solita');
7+
8+
module.exports = {
9+
idlGenerator: 'shank',
10+
programName: 'counter_mpl_stack',
11+
programId: 'Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS',
12+
idlDir,
13+
sdkDir,
14+
binaryInstallDir,
15+
programDir,
16+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "counter-mpl-stack"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib", "lib"]
8+
9+
[features]
10+
no-entrypoint = []
11+
cpi = ["no-entrypoint"]
12+
default = []
13+
14+
[dependencies]
15+
borsh = "0.9"
16+
shank = "0.0.8"
17+
solana-program = "1.10.38"

basics/counter/mpl-stack/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Counter: MPL Stack
2+
3+
This example program is written using Solana native using MPL stack.
4+
5+
6+
## Setup
7+
8+
1. Build the program with `cargo build-bpf`
9+
2. Compile the idl with `shank build`
10+
3. Build the typescript SDK with `yarn solita`
11+
- Temporarily, we have to modify line 58 in ts/generated/accounts/Counter.ts
12+
to `const accountInfo = await connection.getAccountInfo(address, { commitment: "confirmed" });` in order to allow the tests to pass. In the future versions of Solita, this will be fixed.
13+
4. Run tests with `yarn test`
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"version": "0.1.0",
3+
"name": "counter_mpl_stack",
4+
"instructions": [
5+
{
6+
"name": "Increment",
7+
"accounts": [
8+
{
9+
"name": "counter",
10+
"isMut": true,
11+
"isSigner": false,
12+
"desc": "Counter account to increment"
13+
}
14+
],
15+
"args": [],
16+
"discriminant": {
17+
"type": "u8",
18+
"value": 0
19+
}
20+
}
21+
],
22+
"accounts": [
23+
{
24+
"name": "Counter",
25+
"type": {
26+
"kind": "struct",
27+
"fields": [
28+
{
29+
"name": "count",
30+
"type": "u64"
31+
}
32+
]
33+
}
34+
}
35+
],
36+
"metadata": {
37+
"origin": "shank",
38+
"address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS",
39+
"binaryVersion": "0.0.8",
40+
"libVersion": "0.0.8"
41+
}
42+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"version": "0.1.0",
3+
"name": "counter_solana_native",
4+
"instructions": [
5+
{
6+
"name": "Increment",
7+
"accounts": [],
8+
"args": [],
9+
"discriminant": {
10+
"type": "u8",
11+
"value": 0
12+
}
13+
}
14+
],
15+
"accounts": [
16+
{
17+
"name": "Counter",
18+
"type": {
19+
"kind": "struct",
20+
"fields": [
21+
{
22+
"name": "count",
23+
"type": "u64"
24+
}
25+
]
26+
}
27+
}
28+
],
29+
"metadata": {
30+
"origin": "shank",
31+
"address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
preset: 'ts-jest/presets/default',
3+
testEnvironment: 'node',
4+
testTimeout: 100000,
5+
resolver: "ts-jest-resolver",
6+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "counter-mpl-stack",
3+
"version": "0.1.0",
4+
"description": "Counter program written using MPL tooling",
5+
"main": "index.js",
6+
"author": "ngundotra",
7+
"license": "Apache-2.0",
8+
"private": false,
9+
"scripts": {
10+
"start-validator": "solana-test-validator --reset --quiet --bpf-program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS ./target/deploy/counter_solana_native.so",
11+
"run-tests": "jest tests --detectOpenHandles",
12+
"test": "start-server-and-test start-validator http://localhost:8899/health run-tests"
13+
},
14+
"devDependencies": {
15+
"@types/bn.js": "^5.1.1",
16+
"@types/jest": "^29.0.0",
17+
"chai": "^4.3.6",
18+
"jest": "^29.0.2",
19+
"start-server-and-test": "^1.14.0",
20+
"ts-jest": "^28.0.8",
21+
"ts-jest-resolver": "^2.0.0",
22+
"ts-node": "^10.9.1",
23+
"typescript": "^4.8.2"
24+
},
25+
"dependencies": {
26+
"@metaplex-foundation/beet": "^0.6.1",
27+
"@metaplex-foundation/solita": "^0.15.2",
28+
"@solana/web3.js": "^1.56.2"
29+
}
30+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use borsh::{BorshDeserialize, BorshSerialize};
2+
use shank::ShankInstruction;
3+
use solana_program::{
4+
account_info::{next_account_info, AccountInfo},
5+
declare_id,
6+
entrypoint::ProgramResult,
7+
msg,
8+
program::{invoke, invoke_signed},
9+
program_error::ProgramError,
10+
pubkey::Pubkey,
11+
system_instruction,
12+
};
13+
14+
mod state;
15+
use state::*;
16+
17+
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
18+
19+
#[cfg(not(feature = "no-entrypoint"))]
20+
use solana_program::entrypoint;
21+
22+
#[cfg(not(feature = "no-entrypoint"))]
23+
entrypoint!(process_instruction);
24+
25+
#[derive(ShankInstruction, BorshDeserialize, BorshSerialize)]
26+
pub enum Instruction {
27+
#[account(0, writable, name = "counter", desc = "Counter account to increment")]
28+
Increment,
29+
}
30+
31+
pub fn process_instruction(
32+
_program_id: &Pubkey,
33+
accounts: &[AccountInfo],
34+
instruction_data: &[u8],
35+
) -> ProgramResult {
36+
let (instruction_discriminant, instruction_data_inner) = instruction_data.split_at(1);
37+
match instruction_discriminant[0] {
38+
0 => {
39+
msg!("Instruction: Increment");
40+
process_increment_counter(accounts, instruction_data_inner)?;
41+
}
42+
_ => {
43+
msg!("Error: unknown instruction")
44+
}
45+
}
46+
Ok(())
47+
}
48+
49+
pub fn process_increment_counter(
50+
accounts: &[AccountInfo],
51+
instruction_data: &[u8],
52+
) -> Result<(), ProgramError> {
53+
let account_info_iter = &mut accounts.iter();
54+
55+
let counter_account = next_account_info(account_info_iter)?;
56+
assert!(
57+
counter_account.is_writable,
58+
"Counter account must be writable"
59+
);
60+
61+
let mut counter = Counter::try_from_slice(&counter_account.try_borrow_mut_data()?)?;
62+
counter.count += 1;
63+
counter.serialize(&mut *counter_account.data.borrow_mut())?;
64+
65+
msg!("Counter state incremented to {:?}", counter.count);
66+
Ok(())
67+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use borsh::{BorshDeserialize, BorshSerialize};
2+
use shank::ShankAccount;
3+
4+
#[derive(ShankAccount, BorshSerialize, BorshDeserialize, Debug, Clone)]
5+
pub struct Counter {
6+
pub count: u64,
7+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {
2+
Connection,
3+
Keypair,
4+
LAMPORTS_PER_SOL,
5+
Transaction,
6+
TransactionInstruction,
7+
sendAndConfirmTransaction,
8+
SystemProgram
9+
} from '@solana/web3.js';
10+
import {
11+
bignum
12+
} from '@metaplex-foundation/beet';
13+
import { assert } from 'chai';
14+
import { BN } from 'bn.js';
15+
16+
import {
17+
createIncrementInstruction,
18+
Counter,
19+
PROGRAM_ID,
20+
} from '../ts';
21+
22+
function convertBignumToNumber(bignum: bignum): number {
23+
return new BN(bignum).toNumber();
24+
}
25+
26+
describe("Counter Solana Native", () => {
27+
const connection = new Connection("http://localhost:8899");
28+
29+
it("Test allocate counter + increment tx", async () => {
30+
// Randomly generate our wallet
31+
const payerKeypair = Keypair.generate();
32+
const payer = payerKeypair.publicKey;
33+
34+
// Randomly generate the account key
35+
// to sign for setting up the Counter state
36+
const counterKeypair = Keypair.generate();
37+
const counter = counterKeypair.publicKey;
38+
39+
// Airdrop our wallet 1 Sol
40+
await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
41+
42+
// Create a TransactionInstruction to interact with our counter program
43+
const allocIx: TransactionInstruction = SystemProgram.createAccount({
44+
fromPubkey: payer,
45+
newAccountPubkey: counter,
46+
lamports: await connection.getMinimumBalanceForRentExemption(Counter.byteSize),
47+
space: Counter.byteSize,
48+
programId: PROGRAM_ID
49+
})
50+
const incrementIx: TransactionInstruction = createIncrementInstruction({ counter });
51+
let tx = new Transaction().add(allocIx).add(incrementIx);
52+
53+
// Explicitly set the feePayer to be our wallet (this is set to first signer by default)
54+
tx.feePayer = payer;
55+
56+
// Fetch a "timestamp" so validators know this is a recent transaction
57+
tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
58+
59+
// Send transaction to network (local network)
60+
await sendAndConfirmTransaction(
61+
connection,
62+
tx,
63+
[payerKeypair, counterKeypair],
64+
{ skipPreflight: true, commitment: 'confirmed' }
65+
);
66+
67+
// Get the counter account info from network
68+
let count = (await Counter.fromAccountAddress(connection, counter)).count;
69+
assert((new BN(count)).toNumber() === 1, "Expected count to have been 1");
70+
console.log(`[alloc+increment] count is: ${count}`);
71+
});
72+
it("Test allocate tx and increment tx", async () => {
73+
const payerKeypair = Keypair.generate();
74+
const payer = payerKeypair.publicKey;
75+
76+
const counterKeypair = Keypair.generate();
77+
const counter = counterKeypair.publicKey;
78+
79+
await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
80+
81+
// Check allocate tx
82+
const allocIx: TransactionInstruction = SystemProgram.createAccount({
83+
fromPubkey: payer,
84+
newAccountPubkey: counter,
85+
lamports: await connection.getMinimumBalanceForRentExemption(Counter.byteSize),
86+
space: Counter.byteSize,
87+
programId: PROGRAM_ID
88+
})
89+
let tx = new Transaction().add(allocIx);
90+
tx.feePayer = payer;
91+
tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
92+
await sendAndConfirmTransaction(
93+
connection,
94+
tx,
95+
[payerKeypair, counterKeypair],
96+
{ skipPreflight: true, commitment: 'confirmed' }
97+
);
98+
99+
let count = (await Counter.fromAccountAddress(connection, counter)).count;
100+
assert(convertBignumToNumber(count) === 0, "Expected count to have been 0");
101+
console.log(`[allocate] count is: ${count}`);
102+
103+
// Check increment tx
104+
const incrementIx: TransactionInstruction = createIncrementInstruction({ counter });
105+
tx = new Transaction().add(incrementIx);
106+
tx.feePayer = payer;
107+
tx.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
108+
await sendAndConfirmTransaction(
109+
connection,
110+
tx,
111+
[payerKeypair],
112+
{ skipPreflight: true, commitment: 'confirmed' }
113+
);
114+
115+
count = (await Counter.fromAccountAddress(connection, counter)).count;
116+
assert(convertBignumToNumber(count) === 1, "Expected count to have been 1");
117+
console.log(`[increment] count is: ${count}`);
118+
})
119+
})

0 commit comments

Comments
 (0)