CRUD program: create / update / delete (#1226)

* 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
This commit is contained in:
Jon Cinque 2021-03-06 00:20:06 +01:00 committed by GitHub
parent 87a849407f
commit 8fd6f8ec55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1042 additions and 0 deletions

15
Cargo.lock generated
View File

@ -3796,6 +3796,21 @@ dependencies = [
"solana-program",
]
[[package]]
name = "spl-record"
version = "0.1.0"
dependencies = [
"borsh 0.8.1",
"borsh-derive 0.8.1",
"num-derive",
"num-traits",
"solana-program",
"solana-program-test",
"solana-sdk",
"thiserror",
"tokio 0.3.6",
]
[[package]]
name = "spl-shared-memory"
version = "2.0.6"

View File

@ -10,6 +10,7 @@ members = [
"feature-proposal/cli",
"libraries/math",
"memo/program",
"record/program",
"shared-memory/program",
"stake-pool/cli",
"stake-pool/program",

31
record/program/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
name = "spl-record"
version = "0.1.0"
description = "Solana Program Library Record Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
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"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1 @@
ReciQBw6sQKH9TVVJQDnbnJ5W7FP539tPHjZhRF4E9r

View File

@ -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)
}

View File

@ -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"
}
}

View File

@ -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
record/program/src/lib.rs Normal file
View File

@ -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");

View File

@ -0,0 +1,121 @@
//! Program state processor
use {
crate::{
error::RecordError,
instruction::RecordInstruction,
state::{Data, RecordData},
},
borsh::{BorshDeserialize, BorshSerialize},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
program_pack::IsInitialized,
pubkey::Pubkey,
},
};
fn check_authority(authority_info: &AccountInfo, expected_authority: &Pubkey) -> ProgramResult {
if expected_authority != authority_info.key {
msg!("Incorrect record authority provided");
return Err(RecordError::IncorrectAuthority.into());
}
if !authority_info.is_signer {
msg!("Record authority signature missing");
return Err(ProgramError::MissingRequiredSignature);
}
Ok(())
}
/// Instruction processor
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = RecordInstruction::try_from_slice(input)?;
let account_info_iter = &mut accounts.iter();
match instruction {
RecordInstruction::Initialize => {
msg!("RecordInstruction::Initialize");
let data_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let mut account_data = RecordData::try_from_slice(*data_info.data.borrow())?;
if account_data.is_initialized() {
msg!("Record account already initialized");
return Err(ProgramError::AccountAlreadyInitialized);
}
account_data.authority = *authority_info.key;
account_data.version = RecordData::CURRENT_VERSION;
account_data
.serialize(&mut *data_info.data.borrow_mut())
.map_err(|e| e.into())
}
RecordInstruction::Write { offset, data } => {
msg!("RecordInstruction::Write");
let data_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
if !account_data.is_initialized() {
msg!("Record account not initialized");
return Err(ProgramError::UninitializedAccount);
}
check_authority(authority_info, &account_data.authority)?;
let start = RecordData::WRITABLE_START_INDEX + offset as usize;
let end = start + data.len();
if end > data_info.data.borrow().len() {
Err(ProgramError::AccountDataTooSmall)
} else {
data_info.data.borrow_mut()[start..end].copy_from_slice(&data);
Ok(())
}
}
RecordInstruction::SetAuthority => {
msg!("RecordInstruction::SetAuthority");
let data_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let new_authority_info = next_account_info(account_info_iter)?;
let mut account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
if !account_data.is_initialized() {
msg!("Record account not initialized");
return Err(ProgramError::UninitializedAccount);
}
check_authority(authority_info, &account_data.authority)?;
account_data.authority = *new_authority_info.key;
account_data
.serialize(&mut *data_info.data.borrow_mut())
.map_err(|e| e.into())
}
RecordInstruction::CloseAccount => {
msg!("RecordInstruction::CloseAccount");
let data_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let mut account_data = RecordData::try_from_slice(&data_info.data.borrow())?;
if !account_data.is_initialized() {
msg!("Record not initialized");
return Err(ProgramError::UninitializedAccount);
}
check_authority(authority_info, &account_data.authority)?;
let destination_starting_lamports = destination_info.lamports();
let data_lamports = data_info.lamports();
**data_info.lamports.borrow_mut() = 0;
**destination_info.lamports.borrow_mut() = destination_starting_lamports
.checked_add(data_lamports)
.ok_or(RecordError::Overflow)?;
account_data.data = Data::default();
account_data
.serialize(&mut *data_info.data.borrow_mut())
.map_err(|e| e.into())
}
}
}

View File

@ -0,0 +1,90 @@
//! Program state
use {
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
solana_program::{program_pack::IsInitialized, pubkey::Pubkey},
};
/// Struct wrapping data and providing metadata
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
pub struct RecordData {
/// Struct version, allows for upgrades to the program
pub version: u8,
/// The account allowed to update the data
pub authority: Pubkey,
/// The data contained by the account, could be anything serializable
pub data: Data,
}
/// Struct just for data
#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
pub struct Data {
/// The data contained by the account, could be anything or serializable
pub bytes: [u8; Self::DATA_SIZE],
}
impl Data {
/// very small data for easy testing
pub const DATA_SIZE: usize = 8;
}
impl RecordData {
/// Version to fill in on new created accounts
pub const CURRENT_VERSION: u8 = 1;
/// Start of writable account data, after version and authority
pub const WRITABLE_START_INDEX: usize = 33;
}
impl IsInitialized for RecordData {
/// Is initialized
fn is_initialized(&self) -> bool {
self.version == Self::CURRENT_VERSION
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use solana_program::program_error::ProgramError;
/// Version for tests
pub const TEST_VERSION: u8 = 1;
/// Pubkey for tests
pub const TEST_PUBKEY: Pubkey = Pubkey::new_from_array([100; 32]);
/// Bytes for tests
pub const TEST_BYTES: [u8; Data::DATA_SIZE] = [42; Data::DATA_SIZE];
/// Data for tests
pub const TEST_DATA: Data = Data { bytes: TEST_BYTES };
/// RecordData for tests
pub const TEST_RECORD_DATA: RecordData = RecordData {
version: TEST_VERSION,
authority: TEST_PUBKEY,
data: TEST_DATA,
};
#[test]
fn serialize_data() {
let mut expected = vec![];
expected.push(TEST_VERSION);
expected.extend_from_slice(&TEST_PUBKEY.to_bytes());
expected.extend_from_slice(&TEST_DATA.bytes);
assert_eq!(TEST_RECORD_DATA.try_to_vec().unwrap(), expected);
assert_eq!(
RecordData::try_from_slice(&expected).unwrap(),
TEST_RECORD_DATA
);
}
#[test]
fn deserialize_invalid_slice() {
let data = [200; Data::DATA_SIZE - 1];
let mut expected = vec![];
expected.push(TEST_VERSION);
expected.extend_from_slice(&TEST_PUBKEY.to_bytes());
expected.extend_from_slice(&data);
let err: ProgramError = RecordData::try_from_slice(&expected).unwrap_err().into();
assert!(matches!(err, ProgramError::BorshIoError(_)));
}
}

View File

@ -0,0 +1,552 @@
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
#![cfg(feature = "test-bpf")]
use {
borsh::BorshSerialize,
solana_program::{
borsh::get_packed_len,
instruction::{AccountMeta, Instruction, InstructionError},
pubkey::Pubkey,
rent::Rent,
system_instruction,
},
solana_program_test::{processor, ProgramTest, ProgramTestContext},
solana_sdk::{
signature::{Keypair, Signer},
transaction::{Transaction, TransactionError},
transport,
},
spl_record::{
error::RecordError,
id, instruction,
processor::process_instruction,
state::{Data, RecordData},
},
};
fn program_test() -> ProgramTest {
ProgramTest::new("spl_record", id(), processor!(process_instruction))
}
async fn initialize_storage_account(
context: &mut ProgramTestContext,
authority: &Keypair,
account: &Keypair,
data: Data,
) -> transport::Result<()> {
let transaction = Transaction::new_signed_with_payer(
&[
system_instruction::create_account(
&context.payer.pubkey(),
&account.pubkey(),
1.max(Rent::default().minimum_balance(get_packed_len::<RecordData>())),
get_packed_len::<RecordData>() as u64,
&id(),
),
instruction::initialize(&account.pubkey(), &authority.pubkey()),
instruction::write(
&account.pubkey(),
&authority.pubkey(),
0,
data.try_to_vec().unwrap(),
),
],
Some(&context.payer.pubkey()),
&[&context.payer, account, authority],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await
}
#[tokio::test]
async fn initialize_success() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [111u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data.clone())
.await
.unwrap();
let account_data = context
.banks_client
.get_account_data_with_borsh::<RecordData>(account.pubkey())
.await
.unwrap();
assert_eq!(account_data.data, data);
assert_eq!(account_data.authority, authority.pubkey());
assert_eq!(account_data.version, RecordData::CURRENT_VERSION);
}
#[tokio::test]
async fn initialize_with_seed_success() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let seed = "storage";
let account = Pubkey::create_with_seed(&authority.pubkey(), seed, &id()).unwrap();
let data = Data {
bytes: [111u8; Data::DATA_SIZE],
};
let transaction = Transaction::new_signed_with_payer(
&[
system_instruction::create_account_with_seed(
&context.payer.pubkey(),
&account,
&authority.pubkey(),
seed,
1.max(Rent::default().minimum_balance(get_packed_len::<RecordData>())),
get_packed_len::<RecordData>() as u64,
&id(),
),
instruction::initialize(&account, &authority.pubkey()),
instruction::write(&account, &authority.pubkey(), 0, data.try_to_vec().unwrap()),
],
Some(&context.payer.pubkey()),
&[&context.payer, &authority],
context.last_blockhash,
);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let account_data = context
.banks_client
.get_account_data_with_borsh::<RecordData>(account)
.await
.unwrap();
assert_eq!(account_data.data, data);
assert_eq!(account_data.authority, authority.pubkey());
assert_eq!(account_data.version, RecordData::CURRENT_VERSION);
}
#[tokio::test]
async fn initialize_twice_fail() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [111u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let transaction = Transaction::new_signed_with_payer(
&[instruction::initialize(
&account.pubkey(),
&authority.pubkey(),
)],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized)
);
}
#[tokio::test]
async fn write_success() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let new_data = Data {
bytes: [200u8; Data::DATA_SIZE],
};
let transaction = Transaction::new_signed_with_payer(
&[instruction::write(
&account.pubkey(),
&authority.pubkey(),
0,
new_data.try_to_vec().unwrap(),
)],
Some(&context.payer.pubkey()),
&[&context.payer, &authority],
context.last_blockhash,
);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let account_data = context
.banks_client
.get_account_data_with_borsh::<RecordData>(account.pubkey())
.await
.unwrap();
assert_eq!(account_data.data, new_data);
assert_eq!(account_data.authority, authority.pubkey());
assert_eq!(account_data.version, RecordData::CURRENT_VERSION);
}
#[tokio::test]
async fn write_fail_wrong_authority() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let new_data = Data {
bytes: [200u8; Data::DATA_SIZE],
};
let wrong_authority = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[instruction::write(
&account.pubkey(),
&wrong_authority.pubkey(),
0,
new_data.try_to_vec().unwrap(),
)],
Some(&context.payer.pubkey()),
&[&context.payer, &wrong_authority],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(
0,
InstructionError::Custom(RecordError::IncorrectAuthority as u32)
)
);
}
#[tokio::test]
async fn write_fail_unsigned() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let data = Data {
bytes: [200u8; Data::DATA_SIZE],
}
.try_to_vec()
.unwrap();
let transaction = Transaction::new_signed_with_payer(
&[Instruction::new_with_borsh(
id(),
&instruction::RecordInstruction::Write { offset: 0, data },
vec![
AccountMeta::new(account.pubkey(), false),
AccountMeta::new_readonly(authority.pubkey(), false),
],
)],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[tokio::test]
async fn close_account_success() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let recipient = Pubkey::new_unique();
let transaction = Transaction::new_signed_with_payer(
&[instruction::close_account(
&account.pubkey(),
&authority.pubkey(),
&recipient,
)],
Some(&context.payer.pubkey()),
&[&context.payer, &authority],
context.last_blockhash,
);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let account = context
.banks_client
.get_account(recipient)
.await
.unwrap()
.unwrap();
assert_eq!(
account.lamports,
1.max(Rent::default().minimum_balance(get_packed_len::<RecordData>()))
);
}
#[tokio::test]
async fn close_account_fail_wrong_authority() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let wrong_authority = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[Instruction::new_with_borsh(
id(),
&instruction::RecordInstruction::CloseAccount,
vec![
AccountMeta::new(account.pubkey(), false),
AccountMeta::new_readonly(wrong_authority.pubkey(), true),
AccountMeta::new(Pubkey::new_unique(), false),
],
)],
Some(&context.payer.pubkey()),
&[&context.payer, &wrong_authority],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(
0,
InstructionError::Custom(RecordError::IncorrectAuthority as u32)
)
);
}
#[tokio::test]
async fn close_account_fail_unsigned() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let transaction = Transaction::new_signed_with_payer(
&[Instruction::new_with_borsh(
id(),
&instruction::RecordInstruction::CloseAccount,
vec![
AccountMeta::new(account.pubkey(), false),
AccountMeta::new_readonly(authority.pubkey(), false),
AccountMeta::new(Pubkey::new_unique(), false),
],
)],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[tokio::test]
async fn set_authority_success() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let new_authority = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[instruction::set_authority(
&account.pubkey(),
&authority.pubkey(),
&new_authority.pubkey(),
)],
Some(&context.payer.pubkey()),
&[&context.payer, &authority],
context.last_blockhash,
);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let account_data = context
.banks_client
.get_account_data_with_borsh::<RecordData>(account.pubkey())
.await
.unwrap();
assert_eq!(account_data.authority, new_authority.pubkey());
let new_data = Data {
bytes: [200u8; Data::DATA_SIZE],
};
let transaction = Transaction::new_signed_with_payer(
&[instruction::write(
&account.pubkey(),
&new_authority.pubkey(),
0,
new_data.try_to_vec().unwrap(),
)],
Some(&context.payer.pubkey()),
&[&context.payer, &new_authority],
context.last_blockhash,
);
context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
let account_data = context
.banks_client
.get_account_data_with_borsh::<RecordData>(account.pubkey())
.await
.unwrap();
assert_eq!(account_data.data, new_data);
assert_eq!(account_data.authority, new_authority.pubkey());
assert_eq!(account_data.version, RecordData::CURRENT_VERSION);
}
#[tokio::test]
async fn set_authority_fail_wrong_authority() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let wrong_authority = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[Instruction::new_with_borsh(
id(),
&instruction::RecordInstruction::SetAuthority,
vec![
AccountMeta::new(account.pubkey(), false),
AccountMeta::new_readonly(wrong_authority.pubkey(), true),
AccountMeta::new(Pubkey::new_unique(), false),
],
)],
Some(&context.payer.pubkey()),
&[&context.payer, &wrong_authority],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(
0,
InstructionError::Custom(RecordError::IncorrectAuthority as u32)
)
);
}
#[tokio::test]
async fn set_authority_fail_unsigned() {
let mut context = program_test().start_with_context().await;
let authority = Keypair::new();
let account = Keypair::new();
let data = Data {
bytes: [222u8; Data::DATA_SIZE],
};
initialize_storage_account(&mut context, &authority, &account, data)
.await
.unwrap();
let transaction = Transaction::new_signed_with_payer(
&[Instruction::new_with_borsh(
id(),
&instruction::RecordInstruction::SetAuthority,
vec![
AccountMeta::new(account.pubkey(), false),
AccountMeta::new_readonly(authority.pubkey(), false),
AccountMeta::new(Pubkey::new_unique(), false),
],
)],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
assert_eq!(
context
.banks_client
.process_transaction(transaction)
.await
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}