Skip to content

Commit

Permalink
CRUD program: create / update / delete (solana-labs#1226)
Browse files Browse the repository at this point in the history
* Add scaffolding

* Add create / update / delete instructions and tests

* Update SerializationError -> IOError and program id

* Address review feedback

* Make initialize work with `create_with_seed`

* Cargo fmt

* Use offset for writing

* Update crud -> record

* More feedback

* Remove rent

* Update program id

* Use official Solana crates 1.5.10

* Update Cargo lock and toml

* Cargo fmt

* Update record program version to 1.5.11

* Bump compute budget
  • Loading branch information
joncinque authored Mar 5, 2021
1 parent 87a8494 commit 8fd6f8e
Show file tree
Hide file tree
Showing 12 changed files with 1,042 additions and 0 deletions.
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"feature-proposal/cli",
"libraries/math",
"memo/program",
"record/program",
"shared-memory/program",
"stake-pool/cli",
"stake-pool/program",
Expand Down
31 changes: 31 additions & 0 deletions record/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "spl-record"
version = "0.1.0"
description = "Solana Program Library Record Program"
authors = ["Solana Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"

[features]
no-entrypoint = []
test-bpf = []

[dependencies]
borsh = "0.8.1"
borsh-derive = "0.8.1"
num-derive = "0.3"
num-traits = "0.2"
solana-program = "1.5.11"
thiserror = "1.0"

[dev-dependencies]
solana-program-test = "1.5.11"
solana-sdk = "1.5.11"
tokio = { version = "0.3", features = ["macros"]}

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

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
2 changes: 2 additions & 0 deletions record/program/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
1 change: 1 addition & 0 deletions record/program/program-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ReciQBw6sQKH9TVVJQDnbnJ5W7FP539tPHjZhRF4E9r
16 changes: 16 additions & 0 deletions record/program/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Program entrypoint
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]

use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
};

entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process_instruction(program_id, accounts, instruction_data)
}
27 changes: 27 additions & 0 deletions record/program/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Error types
use num_derive::FromPrimitive;
use solana_program::{decode_error::DecodeError, program_error::ProgramError};
use thiserror::Error;

/// Errors that may be returned by the program.
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum RecordError {
/// Incorrect authority provided on update or delete
#[error("Incorrect authority provided on update or delete")]
IncorrectAuthority,

/// Calculation overflow
#[error("Calculation overflow")]
Overflow,
}
impl From<RecordError> for ProgramError {
fn from(e: RecordError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for RecordError {
fn type_of() -> &'static str {
"Record Error"
}
}
173 changes: 173 additions & 0 deletions record/program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//! Program instructions
use crate::id;
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};

/// Instructions supported by the program
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub enum RecordInstruction {
/// Create a new record
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` Record account, must be uninitialized
/// 1. `[]` Record authority
Initialize,

/// Write to the provided record account
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` Record account, must be previously initialized
/// 1. `[signer]` Current record authority
Write {
/// Offset to start writing record, expressed as `u64`.
offset: u64,
/// Data to replace the existing record data
data: Vec<u8>,
},

/// Update the authority of the provided record account
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` Record account, must be previously initialized
/// 1. `[signer]` Current record authority
/// 2. `[]` New record authority
SetAuthority,

/// Close the provided record account, draining lamports to recipient account
///
/// Accounts expected by this instruction:
///
/// 0. `[writable]` Record account, must be previously initialized
/// 1. `[signer]` Record authority
/// 2. `[]` Receiver of account lamports
CloseAccount,
}

/// Create a `RecordInstruction::Initialize` instruction
pub fn initialize(record_account: &Pubkey, authority: &Pubkey) -> Instruction {
Instruction::new_with_borsh(
id(),
&RecordInstruction::Initialize,
vec![
AccountMeta::new(*record_account, false),
AccountMeta::new_readonly(*authority, false),
],
)
}

/// Create a `RecordInstruction::Write` instruction
pub fn write(record_account: &Pubkey, signer: &Pubkey, offset: u64, data: Vec<u8>) -> Instruction {
Instruction::new_with_borsh(
id(),
&RecordInstruction::Write { offset, data },
vec![
AccountMeta::new(*record_account, false),
AccountMeta::new_readonly(*signer, true),
],
)
}

/// Create a `RecordInstruction::SetAuthority` instruction
pub fn set_authority(
record_account: &Pubkey,
signer: &Pubkey,
new_authority: &Pubkey,
) -> Instruction {
Instruction::new_with_borsh(
id(),
&RecordInstruction::SetAuthority,
vec![
AccountMeta::new(*record_account, false),
AccountMeta::new_readonly(*signer, true),
AccountMeta::new_readonly(*new_authority, false),
],
)
}

/// Create a `RecordInstruction::CloseAccount` instruction
pub fn close_account(record_account: &Pubkey, signer: &Pubkey, receiver: &Pubkey) -> Instruction {
Instruction::new_with_borsh(
id(),
&RecordInstruction::CloseAccount,
vec![
AccountMeta::new(*record_account, false),
AccountMeta::new_readonly(*signer, true),
AccountMeta::new(*receiver, false),
],
)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::state::tests::TEST_DATA;
use solana_program::program_error::ProgramError;

#[test]
fn serialize_initialize() {
let instruction = RecordInstruction::Initialize;
let expected = vec![0];
assert_eq!(instruction.try_to_vec().unwrap(), expected);
assert_eq!(
RecordInstruction::try_from_slice(&expected).unwrap(),
instruction
);
}

#[test]
fn serialize_write() {
let data = TEST_DATA.try_to_vec().unwrap();
let offset = 0u64;
let instruction = RecordInstruction::Write {
offset: 0,
data: data.clone(),
};
let mut expected = vec![1];
expected.extend_from_slice(&offset.to_le_bytes());
expected.append(&mut data.try_to_vec().unwrap());
assert_eq!(instruction.try_to_vec().unwrap(), expected);
assert_eq!(
RecordInstruction::try_from_slice(&expected).unwrap(),
instruction
);
}

#[test]
fn serialize_set_authority() {
let instruction = RecordInstruction::SetAuthority;
let expected = vec![2];
assert_eq!(instruction.try_to_vec().unwrap(), expected);
assert_eq!(
RecordInstruction::try_from_slice(&expected).unwrap(),
instruction
);
}

#[test]
fn serialize_close_account() {
let instruction = RecordInstruction::CloseAccount;
let expected = vec![3];
assert_eq!(instruction.try_to_vec().unwrap(), expected);
assert_eq!(
RecordInstruction::try_from_slice(&expected).unwrap(),
instruction
);
}

#[test]
fn deserialize_invalid_instruction() {
let mut expected = vec![12];
expected.append(&mut TEST_DATA.try_to_vec().unwrap());
let err: ProgramError = RecordInstruction::try_from_slice(&expected)
.unwrap_err()
.into();
assert!(matches!(err, ProgramError::BorshIoError(_)));
}
}
13 changes: 13 additions & 0 deletions record/program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Record program
#![deny(missing_docs)]

mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;

// Export current SDK types for downstream users building with a different SDK version
pub use solana_program;

solana_program::declare_id!("ReciQBw6sQKH9TVVJQDnbnJ5W7FP539tPHjZhRF4E9r");
Loading

0 comments on commit 8fd6f8e

Please sign in to comment.