Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,998 changes: 1,998 additions & 0 deletions native-rust-vrf/Cargo.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions native-rust-vrf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "native-rust-vrf"
version = "0.1.0"
edition = "2024"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the current Rust version bundled with the latest Solana / Agave cargo-build-sbf platform-tools, and does it support edition = "2024"?

💡 Result:

The latest Agave / Solana cargo-build-sbf platform-tools uses platform-tools v1.54 (as of March 2026), which bundles a Rust version at least 1.84.1 or newer (v1.47 used 1.84.1; later versions like v1.54 likely use 1.94+ given Agave discussions). It does not support edition = "2024", as Cargo 1.84.0 explicitly lacks edition2024 support (requires Cargo 1.85+ or nightly), and issues persist into late 2025 without evidence of upgrade by April 2026.

Citations:


Downgrade edition = "2024" to edition = "2021".

Edition 2024 is not supported by the latest Solana/Agave cargo-build-sbf platform-tools (v1.54 as of March 2026). The bundled Cargo version lacks edition 2024 support and requires Cargo 1.85+. Contributors will encounter a build failure when running cargo build-sbf. Align with other examples in the repo by using edition = "2021".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/Cargo.toml` at line 4, Update the Cargo.toml edition setting
from "2024" to "2021" so the crate uses the supported Rust edition for the
current Solana/Agave cargo-build-sbf toolchain; locate the edition = "2024"
entry in Cargo.toml and change it to edition = "2021" to align with other
examples and avoid build failures.


[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
borsh = "1.5.7"
ephemeral-vrf-sdk = "0.2.3"
solana-program = "2.2.1"
solana-sdk-ids = "2.2.1"
solana-system-interface = { version = "1.0.0", features = ["bincode"] }
58 changes: 58 additions & 0 deletions native-rust-vrf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# rust-native-vrf-example

Native Solana program (no Anchor) that uses **MagicBlock Ephemeral VRF** via `ephemeral-vrf-sdk`: the user **requests** randomness; the **VRF program** **callbacks** with 32 bytes; you **derive** a value (e.g. 1–6) and store it in `PlayerState`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent dice range between intro and callback section.

Line 3 says randomness is mapped to 1–6 ("roll-dice") while Line 26 says 1–10. Pick one and align with what callback_consume_randomness.rs actually computes — the PR is titled "roll-dice" so 1–6 is the natural choice. Readers copying snippets will otherwise be confused.

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/README.md` at line 3, The README currently describes two
different dice ranges; update the documentation to consistently use 1–6 to match
the PR intent and the implementation in callback_consume_randomness.rs: change
the intro sentence and the later occurrence (around line 26) to state randomness
is mapped to 1–6, and verify PlayerState population and any examples/snippets
reference the same 1–6 mapping so docs match the logic in
callback_consume_randomness.rs.


**Program id:** `5hExoUW5SvPxTHTcz3ok117BoLa1TzzG6KZZfWD23DfD` (see `src/lib.rs`).

---

## Instructions (what each one does)

### `InitializePlayer` (Borsh: wallet → your program)

- **Caller:** user (signs as **authority**).
- **Effect:** creates the **player** PDA for seeds `["player", authority]`, writes `PlayerState` (discriminator, `random_value` initially `0`, bump).
- **File:** `src/instructions/initialize_player.rs`

### `RequestRandomness { client_seed: u8 }` (Borsh: wallet → your program)

- **Caller:** user (payer signs).
- **Effect:** checks queue / accounts, builds `RequestRandomnessParams` and CPIs the **ephemeral VRF** program with `create_request_randomness_ix`. Your program signs the CPI using the **identity** PDA (`["identity"]` under this program). The request encodes *which* callback to run and *which* accounts the VRF will pass when it invokes you (e.g. the player PDA as writable). **This instruction does not** set the final roll; it only records the request on-chain and triggers the VRF.
- **File:** `src/instructions/request_randomness.rs`

### VRF callback → `CallbackConsumeRandomness` (not plain Borsh on the same enum)

- **Caller:** the **VRF** program, not the user. Instruction data = fixed **8-byte** prefix (see `vrf_lite::CALLBACK_CONSUME_RANDOMNESS`) **+ 32** random bytes (40 bytes total). `src/processor.rs` routes this **before** `VrfInstruction::try_from_slice`, because it is not the same layout as your wallet Borsh instructions.
- **Effect:** verifies `VRF_PROGRAM_IDENTITY` is the signer, parses the 32-byte seed, maps it (e.g. `rnd::random_u8_with_range` → 1–10), updates `PlayerState.random_value` on the player PDA.
- **Files:** `src/vrf_lite.rs`, `src/instructions/callback_consume_randomness.rs`

---

## Build and deploy

```bash
cargo build-sbf
solana program deploy target/deploy/<your_program>.so --program-id reflex_program-keypair.json
```
Comment on lines +33 to +36
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stray reflex_program-keypair.json from another project.

The deploy command references reflex_program-keypair.json, but nothing else in this PR or crate is named "reflex". This appears copied from an unrelated example and will fail for anyone following the README literally. Use a placeholder consistent with the <your_program>.so style, or native-rust-vrf-keypair.json.

📝 Proposed fix
 cargo build-sbf
-solana program deploy target/deploy/<your_program>.so --program-id reflex_program-keypair.json
+solana program deploy target/deploy/native_rust_vrf.so --program-id native-rust-vrf-keypair.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/README.md` around lines 33 - 36, The README's deploy command
wrongly references reflex_program-keypair.json from another project; update the
solana program deploy example to use a matching placeholder for the keypair
(e.g., replace reflex_program-keypair.json with <your_program>-keypair.json or
native-rust-vrf-keypair.json) so it aligns with the <your_program>.so artifact
and the rest of this crate.


Upgrade the same program id when you change the `.so` (redeploy with the same program keypair).

---

## Client tests (`test/`)

```bash
cd test
npm install
# Off-chain Borsh checks only
npm test
# On-chain: devnet (or set SOLANA_RPC_URL / SOLANA_WS_URL); needs payer keypair
RUN_INTEGRATION=1 npm test
```

- **`RUN_INIT_INTEGRATION=1`:** also runs the `initialize_player` chain test (default off so you can focus on VRF if the player PDA already exists).
- **`AUTO_INIT_PLAYER=1`:** with `RUN_INTEGRATION=1`, creates the player PDA if missing before the VRF test.

`PROGRAM_ID` in the client matches `getTestProgramId()` in `test/utils.ts` (override with env).

---
25 changes: 25 additions & 0 deletions native-rust-vrf/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// import crates / libraries
use crate::processor;
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
};

// declare and export the program's entrypoint
entrypoint!(process_instruction);

// program entrypoint's implementation
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// Log a message indicating the program ID, number of accounts, and instruction data
msg!(
"process_instruction: Program {} is executed with {} account(s) and the following data={:?}",
program_id,
accounts.len(),
_instruction_data
);
processor::process_instruction(program_id, accounts, _instruction_data)?;
Ok(())
}
Comment on lines +11 to +25
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Drop the leading underscore on the used parameter, and simplify the return.

_instruction_data is conventionally read as "intentionally unused", but it is in fact passed to both msg! (Line 21) and processor::process_instruction (Line 23). Rename to instruction_data so the signature reflects intent. The trailing ?; Ok(()) is also redundant — just return the inner call. Separately, consider gating the entry msg! (which prints the full instruction byte slice via {:?}) — on a real cluster this consumes CU and bloats logs on every transaction, and other crates in this repo will likely be copy/pasted from this example.

♻️ Proposed fix
 pub fn process_instruction(
     program_id: &Pubkey,
     accounts: &[AccountInfo],
-    _instruction_data: &[u8],
+    instruction_data: &[u8],
 ) -> ProgramResult {
-    // Log a message indicating the program ID, number of accounts, and instruction data
-    msg!(
-        "process_instruction: Program {} is executed with {} account(s) and the following data={:?}",
-        program_id,
-        accounts.len(),
-        _instruction_data
-    );
-    processor::process_instruction(program_id, accounts, _instruction_data)?;
-    Ok(())
+    msg!(
+        "process_instruction: program {} accounts={} data_len={}",
+        program_id,
+        accounts.len(),
+        instruction_data.len()
+    );
+    processor::process_instruction(program_id, accounts, instruction_data)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// Log a message indicating the program ID, number of accounts, and instruction data
msg!(
"process_instruction: Program {} is executed with {} account(s) and the following data={:?}",
program_id,
accounts.len(),
_instruction_data
);
processor::process_instruction(program_id, accounts, _instruction_data)?;
Ok(())
}
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
msg!(
"process_instruction: program {} accounts={} data_len={}",
program_id,
accounts.len(),
instruction_data.len()
);
processor::process_instruction(program_id, accounts, instruction_data)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/src/entrypoint.rs` around lines 11 - 25, The entry function
process_instruction uses a parameter named _instruction_data which implies it's
unused but it's both logged and forwarded; rename the parameter to
instruction_data in the fn signature and all uses (the msg! call and the
processor::process_instruction invocation) so intent is clear, and simplify the
return by returning the result of processor::process_instruction(...) directly
instead of using ?; Ok(()). Also gate the verbose msg! that prints the full
instruction bytes (around the existing msg! call) behind a compile-time feature
or debug flag (e.g., cfg(feature = "verbose_logs") or cfg!(debug_assertions)) to
avoid bloating logs and consuming compute on mainnet.

34 changes: 34 additions & 0 deletions native-rust-vrf/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use solana_program::program_error::ProgramError;

/// Program-specific errors. Custom codes start at 0x1770 to avoid colliding with common SPL ranges.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum VrfError {
AlreadyInitialized = 0x1770,
InvalidPda = 0x1771,
InvalidInstructionData = 0x1772,
AccountOrder = 0x1773,
MissingSignature = 0x1774,
InvalidSystemProgram = 0x1775,
ExpectedUnallocatedPda = 0x1776,
/// `callback_consume` must be invoked by the VRF (prefix + 32B); wallet cannot trigger it this way.
CallbackUnexpectedUserInvoke = 0x1777,
/// First account must be `ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY` and signer.
InvalidVrfProgramIdentity = 0x1778,
/// VRF callback `instruction_data` is not 8+32 with the expected prefix.
InvalidCallbackData = 0x1779,
/// `oracle_queue` must match the queue used with this cluster (we pin `DEFAULT_QUEUE` from the SDK).
InvalidOracleQueue = 0x177a,
/// `program identity` PDA (seeds `[identity]`) is wrong.
InvalidProgramIdentityPda = 0x177b,
/// `request_randomness` requires an initialized `Player` account.
PlayerNotInitialized = 0x177c,
/// PDA is not owned by this program or bad discriminator.
InvalidPlayerState = 0x177d,
}

impl From<VrfError> for ProgramError {
fn from(e: VrfError) -> Self {
ProgramError::Custom(e as u32)
}
}
64 changes: 64 additions & 0 deletions native-rust-vrf/src/instructions/callback_consume_randomness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::{error::VrfError, state::PlayerState, vrf_lite};
use borsh::BorshDeserialize;
use ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY;
use ephemeral_vrf_sdk::rnd;
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
};

/// `[0] vrf_program_identity` — `ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY`, **signer** (VRF)
/// `[1] player` (mut) — the same PDA you passed in the request’s `accounts_metas`
pub fn process(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
if accounts.len() < 2 {
return Err(VrfError::AccountOrder.into());
}
let vrf_id = &accounts[0];
let player = &accounts[1];

if vrf_id.key != &VRF_PROGRAM_IDENTITY {
return Err(VrfError::InvalidVrfProgramIdentity.into());
}
if !vrf_id.is_signer {
return Err(VrfError::InvalidVrfProgramIdentity.into());
}

let randomness: &[u8; 32] = vrf_lite::parse_vrf_callback_randomness(instruction_data)
.map_err(|_| VrfError::InvalidCallbackData)?;

if !player.is_writable {
return Err(VrfError::AccountOrder.into());
}
if player.owner != program_id {
return Err(VrfError::InvalidPlayerState.into());
}

let mut p = PlayerState::try_from_slice(&player.try_borrow_data()?).map_err(|_| {
if player.data_is_empty() {
VrfError::PlayerNotInitialized
} else {
VrfError::InvalidPlayerState
}
})?;
if p.discriminator != crate::state::DISCRIMINATOR_PLAYER {
return Err(VrfError::InvalidPlayerState.into());
}
Comment on lines +42 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

try_from_slice is strict on trailing bytes — fine today, brittle if PlayerState ever grows or the account is realloc'd.

borsh::BorshDeserialize::try_from_slice rejects input with leftover bytes. Today the PDA is allocated to exactly PlayerState::LEN so this is correct, but if a future migration grows the struct or pre-allocates extra space, every callback will start failing with InvalidPlayerState. Consider being explicit about the slice length:

♻️ Suggested change
-    let mut p = PlayerState::try_from_slice(&player.try_borrow_data()?).map_err(|_| {
+    let data_ref = player.try_borrow_data()?;
+    let mut p = PlayerState::try_from_slice(&data_ref[..PlayerState::LEN]).map_err(|_| {
         if player.data_is_empty() {
             VrfError::PlayerNotInitialized
         } else {
             VrfError::InvalidPlayerState
         }
     })?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let mut p = PlayerState::try_from_slice(&player.try_borrow_data()?).map_err(|_| {
if player.data_is_empty() {
VrfError::PlayerNotInitialized
} else {
VrfError::InvalidPlayerState
}
})?;
if p.discriminator != crate::state::DISCRIMINATOR_PLAYER {
return Err(VrfError::InvalidPlayerState.into());
}
let data_ref = player.try_borrow_data()?;
let mut p = PlayerState::try_from_slice(&data_ref[..PlayerState::LEN]).map_err(|_| {
if player.data_is_empty() {
VrfError::PlayerNotInitialized
} else {
VrfError::InvalidPlayerState
}
})?;
if p.discriminator != crate::state::DISCRIMINATOR_PLAYER {
return Err(VrfError::InvalidPlayerState.into());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/src/instructions/callback_consume_randomness.rs` around lines
42 - 51, The Borsh try_from_slice is failing if the account buffer has trailing
bytes; update the PlayerState deserialization to explicitly read only the first
PlayerState::LEN bytes: first bind let data = player.try_borrow_data()?; if
data.len() < PlayerState::LEN return VrfError::PlayerNotInitialized; then call
PlayerState::try_from_slice(&data[..PlayerState::LEN]) to produce p and keep the
existing discriminator check (p.discriminator vs
crate::state::DISCRIMINATOR_PLAYER). This ensures extra allocated bytes won't
break callback_consume_randomness.


// p.random_value = rnd::random_u64(randomness);
let roll_1_to_6 = rnd::random_u8_with_range(randomness, 1, 6) as u64;
p.random_value = roll_1_to_6;
Comment on lines +53 to +55
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Drop the stale commented-out alternative and reconsider clamping randomness to 1..=6 here.

Two things:

  1. The commented // p.random_value = rnd::random_u64(randomness); is a debug artifact — please remove it (the file’s own vrf_lite and the integration test call this surface a "dice roll", but the original intent is preserved in git history).
  2. Clamping random_value to 1..=6 inside the callback is what makes pollUntilPlayerRandomValueChanges in test/vrfRequest.test.ts flaky on repeated runs (1/6 collision with the previously stored roll). If you want to keep this as a "dice roll" example, consider also bumping a separate request_id/nonce in PlayerState so tests can poll on a strictly monotonic field.

Related: see the test-side comment on vrfRequest.test.ts Lines 125-138.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/src/instructions/callback_consume_randomness.rs` around lines
53 - 55, Remove the stale commented line and stop clamping the randomness to
1..=6 inside the callback: delete the commented `// p.random_value =
rnd::random_u64(randomness);` and replace the use of
`rnd::random_u8_with_range(randomness, 1, 6)` for `p.random_value` with either
the full random value (e.g., `rnd::random_u64(randomness)`) or store the raw
randomness elsewhere; additionally, to preserve the "dice roll" example without
breaking test polling, add a monotonic field (e.g., `request_id` or `nonce`) to
`PlayerState` and increment it in the same callback so
`pollUntilPlayerRandomValueChanges` can reliably wait on a strictly increasing
field while `p.random_value` can remain a bounded display value if desired;
update references to `p.random_value`, the callback function handling
randomness, and tests that poll `pollUntilPlayerRandomValueChanges` accordingly.


let out = borsh::to_vec(&p).map_err(|_| ProgramError::InvalidAccountData)?;
let mut data = player.try_borrow_mut_data()?;
if out.len() > data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
data[..out.len()].copy_from_slice(&out);
Ok(())
}
90 changes: 90 additions & 0 deletions native-rust-vrf/src/instructions/initialize_player.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::{
error::VrfError,
state::{self, PlayerState},
};
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
msg,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use solana_sdk_ids::system_program;
use solana_system_interface::instruction as system_instruction;


/// Accounts: `[0] player authority (signer, mut)`, `[1] player PDA (mut)`,
/// `[2] system program`.
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
if accounts.len() < 3 {
return Err(VrfError::AccountOrder.into());
}
let authority = &accounts[0];
let player_pda = &accounts[1];
let system_info = &accounts[2];

if !authority.is_signer {
return Err(VrfError::MissingSignature.into());
}
if *system_info.key != system_program::ID {
return Err(VrfError::InvalidSystemProgram.into());
}
if !player_pda.is_writable {
return Err(VrfError::AccountOrder.into());
}

let (expected_pda, bump) = state::find_player_pda(authority.key, program_id);
if player_pda.key != &expected_pda {
return Err(VrfError::InvalidPda.into());
}

if player_pda.owner == program_id
&& player_pda.data_len() >= 1
&& player_pda.try_borrow_data()?[0] == state::DISCRIMINATOR_PLAYER
{
return Err(VrfError::AlreadyInitialized.into());
}

if player_pda.lamports() > 0
&& *player_pda.owner != system_program::ID
&& *player_pda.owner != *program_id
{
return Err(VrfError::ExpectedUnallocatedPda.into());
}

let space = PlayerState::LEN;
let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);
let player = PlayerState::new(bump);

let bump_seed = [bump];
let signer: &[&[u8]] = &[state::PLAYER_SEED, authority.key.as_ref(), &bump_seed];

invoke_signed(
&system_instruction::create_account(
authority.key,
player_pda.key,
lamports,
space as u64,
program_id,
),
&[authority.clone(), player_pda.clone(), system_info.clone()],
&[signer],
)?;
Comment on lines +51 to +76
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Pre-funded PDA prevents initialization (minor griefing vector).

If the PDA already holds lamports (owner = system program, no data), the early checks pass, but system_instruction::create_account fails because the destination already has lamports. For an example program this is acceptable, but worth either:

  • Documenting the precondition (PDA must be empty), or
  • Falling back to allocate + assign + transfer of the rent delta when player_pda.lamports() > 0 && owner == system_program::ID.

Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@native-rust-vrf/src/instructions/initialize_player.rs` around lines 51 - 76,
The current initialize_player logic uses system_instruction::create_account
which fails if the PDA already holds lamports (owner == system_program::ID) even
though you only checked for non-zero lamports with other owners; update
initialize_player to handle the pre-funded-PDA case: when player_pda.lamports()
> 0 && *player_pda.owner == system_program::ID, instead of calling
create_account, perform the allocate + assign + transfer sequence (allocate
space = PlayerState::LEN, assign to *program_id, and transfer the
rent.minimum_balance(PlayerState::LEN) minus existing lamports) using
invoke_signed, or alternatively add a clear comment documenting the precondition
that the PDA must be fully empty before calling create_account; reference the
create_account invocation, the player_pda.lamports() check, PlayerState::LEN,
and the signer seed (state::PLAYER_SEED, authority.key, bump) when implementing
the alternate path so the same signer seeds are used for
allocate/assign/transfer.


let data = borsh::to_vec(&player).map_err(|_| ProgramError::InvalidAccountData)?;
let mut dst = player_pda.try_borrow_mut_data()?;
if data.len() > dst.len() {
return Err(ProgramError::AccountDataTooSmall);
}
dst[..data.len()].copy_from_slice(&data);
msg!(
"initialize_player: ok authority={} pda={}",
authority.key,
player_pda.key
);
Ok(())
}
5 changes: 5 additions & 0 deletions native-rust-vrf/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Per-instruction handlers. `processor` only decodes the enum and dispatches here.

pub mod callback_consume_randomness;
pub mod initialize_player;
pub mod request_randomness;
Loading