-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
465c575
commit c22d54e
Showing
4 changed files
with
181 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,25 @@ | ||
# ProgramDerivedAddresses | ||
# Program Derived Addresses | ||
|
||
**ProgramDerivedAddresses** is a ... | ||
|
||
## API | ||
- [`Consts`](api/src/consts.rs) – Program constants. | ||
- [`Error`](api/src/error.rs) – Custom program errors. | ||
- [`Event`](api/src/event.rs) – Custom program events. | ||
- [`Instruction`](api/src/instruction.rs) – Declared instructions. | ||
This program demonstrates how to derive addresses. It will use a PDA to store a counter of visits and increment it. | ||
|
||
## Instructions | ||
- [`Hello`](program/src/hello.rs) – Hello ... | ||
## Building | ||
|
||
## State | ||
- [`User`](api/src/state/user.rs) – User ... | ||
```sh | ||
cargo build-sbf | ||
|
||
``` | ||
## Tests | ||
|
||
To run the test suit, use the Solana toolchain: | ||
``` | ||
cargo test-sbf | ||
This project includes both: | ||
- Rust tests: [`program/tests`](/program/tests) directory. | ||
- Node.js tests using [Bankrun](https://kevinheavey.github.io/solana-bankrun/): [`tests`](/tests) directory. | ||
|
||
```sh | ||
# rust tests | ||
cargo test-sbf | ||
|
||
# node tests | ||
pnpm build-and-test # this will also build the program | ||
#or | ||
pnpm test # if you have already built the program | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } from '@solana/web3.js'; | ||
import * as borsh from 'borsh'; | ||
import { assert } from 'chai'; | ||
import { describe, it } from 'mocha'; | ||
import { BanksClient, ProgramTestContext, start } from 'solana-bankrun'; | ||
|
||
type PageVisits = { | ||
page_visits: number; | ||
bump: number; | ||
}; | ||
|
||
const pageVisitsSchema: borsh.Schema = { | ||
struct: { | ||
discriminator: 'u64', | ||
page_visits: 'u32', | ||
bump: 'u8', | ||
}, | ||
}; | ||
|
||
const createPageVisitsBuffer = (data: PageVisits): Buffer => { | ||
const pageVisits = Buffer.alloc(4); | ||
pageVisits.writeUInt32LE(data.page_visits, 0); | ||
const bump = Buffer.alloc(1); | ||
bump.writeUInt8(data.bump, 0); | ||
return Buffer.concat([pageVisits, bump]); | ||
}; | ||
|
||
describe('program derived addresses program', async () => { | ||
const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35'); | ||
|
||
let context: ProgramTestContext; | ||
let client: BanksClient; | ||
let payer: Keypair; | ||
|
||
const testUser = Keypair.generate(); | ||
|
||
const instructionDiscriminators = { | ||
create: Buffer.from([0]), | ||
increment: Buffer.from([1]), | ||
}; | ||
|
||
const derivePageVisitsPDA = async (user: PublicKey) => { | ||
const seed = Buffer.from('program-derived-addresses'); | ||
return PublicKey.findProgramAddressSync([seed, user.toBuffer()], PROGRAM_ID); | ||
}; | ||
|
||
before(async () => { | ||
context = await start([{ name: 'program_derived_addresses_program', programId: PROGRAM_ID }], []); | ||
client = context.banksClient; | ||
payer = context.payer; | ||
}); | ||
|
||
it('should create a page visits tracking PDA', async () => { | ||
const [pageVisitsPDA] = await derivePageVisitsPDA(testUser.publicKey); | ||
|
||
// create the page visits data | ||
const pageVisits: PageVisits = { page_visits: 0, bump: 0 }; | ||
const pageVisitsBuffer = createPageVisitsBuffer(pageVisits); | ||
const data = Buffer.concat([instructionDiscriminators.create, pageVisitsBuffer]); | ||
|
||
// create the create instruction | ||
const createIx = new TransactionInstruction({ | ||
programId: PROGRAM_ID, | ||
keys: [ | ||
{ pubkey: payer.publicKey, isSigner: true, isWritable: true }, | ||
{ pubkey: testUser.publicKey, isSigner: false, isWritable: false }, | ||
{ pubkey: pageVisitsPDA, isSigner: false, isWritable: true }, | ||
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, | ||
], | ||
data, | ||
}); | ||
|
||
// send the create transaction | ||
const createTx = new Transaction(); | ||
createTx.recentBlockhash = context.lastBlockhash; | ||
createTx.add(createIx).sign(payer); | ||
|
||
// process the transaction | ||
await client.processTransaction(createTx); | ||
|
||
// fetch the counter account data | ||
const pageVisitsInfo = await client.getAccount(pageVisitsPDA); | ||
assert(pageVisitsInfo !== null, 'account should exist'); | ||
}); | ||
|
||
it('should visit the page and get 1 visit!', async () => { | ||
const [pageVisitsPDA] = await derivePageVisitsPDA(testUser.publicKey); | ||
|
||
// create the increment instruction | ||
const incrementIx = new TransactionInstruction({ | ||
programId: PROGRAM_ID, | ||
keys: [{ pubkey: pageVisitsPDA, isSigner: false, isWritable: true }], | ||
data: instructionDiscriminators.increment, | ||
}); | ||
|
||
// send the increment transaction | ||
const incrementTx = new Transaction(); | ||
incrementTx.recentBlockhash = context.lastBlockhash; | ||
incrementTx.add(incrementIx).sign(payer); | ||
|
||
// process the transaction | ||
await client.processTransaction(incrementTx); | ||
|
||
// fetch the account data | ||
const pageVisitsInfo = await client.getAccount(pageVisitsPDA); | ||
assert(pageVisitsInfo !== null, 'account should exist'); | ||
|
||
const data = borsh.deserialize(pageVisitsSchema, pageVisitsInfo?.data) as PageVisits; | ||
|
||
assert(data.page_visits === 1, 'page visits should be 1'); | ||
}); | ||
|
||
it('should visit the page and get 2 visits!', async () => { | ||
const [pageVisitsPDA] = await derivePageVisitsPDA(testUser.publicKey); | ||
|
||
// create the increment instruction | ||
const incrementIx = new TransactionInstruction({ | ||
programId: PROGRAM_ID, | ||
keys: [{ pubkey: pageVisitsPDA, isSigner: false, isWritable: true }], | ||
data: instructionDiscriminators.increment, | ||
}); | ||
|
||
// get last blockhash | ||
const [blockhash, _block_height] = await client.getLatestBlockhash(); | ||
|
||
// send the increment transaction | ||
const incrementTx = new Transaction(); | ||
incrementTx.recentBlockhash = blockhash; | ||
incrementTx.add(incrementIx).sign(payer); | ||
|
||
// process the transaction | ||
await client.processTransaction(incrementTx); | ||
|
||
// fetch the account data | ||
const pageVisitsInfo = await client.getAccount(pageVisitsPDA); | ||
assert(pageVisitsInfo !== null, 'account should exist'); | ||
|
||
const data = borsh.deserialize(pageVisitsSchema, pageVisitsInfo?.data) as PageVisits; | ||
|
||
assert(data.page_visits === 2, 'page visits should be 2'); | ||
}); | ||
|
||
it('should read all the visits of the page', async () => { | ||
const [pageVisitsPDA] = await derivePageVisitsPDA(testUser.publicKey); | ||
|
||
// fetch the account data | ||
const pageVisitsInfo = await client.getAccount(pageVisitsPDA); | ||
assert(pageVisitsInfo !== null, 'account should exist'); | ||
|
||
const data = borsh.deserialize(pageVisitsSchema, pageVisitsInfo?.data) as PageVisits; | ||
|
||
assert(data.page_visits === 2, 'page visits should be 2'); | ||
}); | ||
}); |