From 6dda061dd042e64ab8de889e3441e1bac7551b99 Mon Sep 17 00:00:00 2001 From: WhoSoup Date: Mon, 5 Jul 2021 14:24:16 +0200 Subject: [PATCH] serialize via borsh --- program/Cargo.toml | 4 +- program/src/account.rs | 204 +++++++------------------------------ program/src/error.rs | 10 +- program/src/instruction.rs | 141 ++++++------------------- program/src/processor.rs | 106 ++++++++++++++----- test/index.ts | 5 +- 6 files changed, 158 insertions(+), 312 deletions(-) diff --git a/program/Cargo.toml b/program/Cargo.toml index 6886017..912af22 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -6,13 +6,11 @@ edition = "2018" [dependencies] solana-program = "1.6.0" -byteorder = "1" spl-token = { version = "3.1.0", features = ["no-entrypoint"]} -spl-associated-token-account = { version = "1.0.2", features = ["no-entrypoint"]} num-derive = "0.3" num-traits = "0.2" thiserror = "1" -arrayref = "0.3.6" +borsh = "0.8.1" [features] diff --git a/program/src/account.rs b/program/src/account.rs index dbbffe2..eda5f66 100644 --- a/program/src/account.rs +++ b/program/src/account.rs @@ -1,21 +1,15 @@ use crate::error::TreasuryError; -use arrayref::{array_mut_ref, array_ref}; -use arrayref::{array_refs, mut_array_refs}; +use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::account_info::AccountInfo; -use solana_program::entrypoint::ProgramResult; -use solana_program::program::invoke_signed; +use solana_program::msg; use solana_program::program_error::ProgramError; +use solana_program::program_pack::Pack; use solana_program::pubkey::Pubkey; use solana_program::pubkey::MAX_SEED_LEN; -use solana_program::system_instruction; -use solana_program::{ - msg, - program_pack::{Pack, Sealed}, -}; use spl_token::state::{Account as SPLAccount, Mint}; #[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize)] pub struct Settings { pub token: Pubkey, pub fee_recipient: Pubkey, @@ -24,8 +18,6 @@ pub struct Settings { pub launch_fee_zoints: u64, } -impl Sealed for Settings {} - impl Settings { pub fn program_address(program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[b"settings"], program_id) @@ -40,28 +32,6 @@ impl Settings { Ok(seed) } - pub fn create_account<'a>( - funder_info: &AccountInfo<'a>, - settings_info: &AccountInfo<'a>, - rent: solana_program::rent::Rent, - program_id: &Pubkey, - ) -> ProgramResult { - let seed = Settings::verify_program_key(settings_info.key, program_id)?; - let lamports = rent.minimum_balance(Settings::LEN); - let space = Settings::LEN as u64; - invoke_signed( - &system_instruction::create_account( - funder_info.key, - settings_info.key, - lamports, - space, - program_id, - ), - &[funder_info.clone(), settings_info.clone()], - &[&[b"settings", &[seed]]], - ) - } - pub fn verify_fee_recipient(&self, key: &Pubkey) -> Result<(), ProgramError> { match self.fee_recipient == *key { true => Ok(()), @@ -110,51 +80,12 @@ impl Settings { } } -impl Pack for Settings { - const LEN: usize = 32 + 32 + 32 + 8 + 8; - fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, Settings::LEN]; - let (token, fee_recipient, price_authority, launch_fee_user, launch_fee_zoints) = - array_refs![src, 32, 32, 32, 8, 8]; - let token = Pubkey::new(token); - let fee_recipient = Pubkey::new(fee_recipient); - let price_authority = Pubkey::new(price_authority); - let launch_fee_user = u64::from_le_bytes(*launch_fee_user); - let launch_fee_zoints = u64::from_le_bytes(*launch_fee_zoints); - Ok(Settings { - token, - fee_recipient, - price_authority, - launch_fee_user, - launch_fee_zoints, - }) - } - - fn pack_into_slice(&self, dst: &mut [u8]) { - let dst = array_mut_ref![dst, 0, Settings::LEN]; - let ( - token_dst, - fee_recipient_dst, - price_authority_dst, - launch_fee_user_dst, - launch_fee_zoints_dst, - ) = mut_array_refs![dst, 32, 32, 32, 8, 8]; - *token_dst = self.token.to_bytes(); - *fee_recipient_dst = self.fee_recipient.to_bytes(); - *price_authority_dst = self.price_authority.to_bytes(); - *launch_fee_user_dst = self.launch_fee_user.to_le_bytes(); - *launch_fee_zoints_dst = self.launch_fee_zoints.to_le_bytes(); - } -} - #[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize)] pub struct UserTreasury { pub authority: Pubkey, } -impl Sealed for UserTreasury {} - impl UserTreasury { pub fn program_address(user: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[b"user", &user.to_bytes()], program_id) @@ -172,61 +103,14 @@ impl UserTreasury { } Ok(seed) } - - pub fn create_account<'a>( - funder_info: &AccountInfo<'a>, - treasury_info: &AccountInfo<'a>, - creator_info: &AccountInfo<'a>, - rent: solana_program::rent::Rent, - program_id: &Pubkey, - ) -> ProgramResult { - let seed = - UserTreasury::verify_program_key(treasury_info.key, creator_info.key, program_id)?; - let lamports = rent.minimum_balance(UserTreasury::LEN); - let space = UserTreasury::LEN as u64; - invoke_signed( - &system_instruction::create_account( - funder_info.key, - treasury_info.key, - lamports, - space, - program_id, - ), - &[funder_info.clone(), treasury_info.clone()], - &[&[b"user", &creator_info.key.to_bytes(), &[seed]]], - )?; - - let user_treasury = Self { - authority: *creator_info.key, - }; - Self::pack(user_treasury, &mut treasury_info.data.borrow_mut()) - } -} - -impl Pack for UserTreasury { - const LEN: usize = 32; - fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, UserTreasury::LEN]; - - let authority = Pubkey::new(src); - - Ok(UserTreasury { authority }) - } - - fn pack_into_slice(&self, dst: &mut [u8]) { - let authority_dst = array_mut_ref![dst, 0, UserTreasury::LEN]; - *authority_dst = self.authority.to_bytes(); - } } #[repr(C)] -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize)] pub struct ZointsTreasury { pub authority: Pubkey, } -impl Sealed for ZointsTreasury {} - impl ZointsTreasury { pub fn program_address(name: &Vec, program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[b"zoints", name], program_id) @@ -265,51 +149,6 @@ impl ZointsTreasury { false => Err(TreasuryError::ZointsTreasuryNameInvalidCharacters.into()), } } - - pub fn create_account<'a>( - funder_info: &AccountInfo<'a>, - treasury_info: &AccountInfo<'a>, - name: &Vec, - authority_info: &AccountInfo<'a>, - rent: solana_program::rent::Rent, - program_id: &Pubkey, - ) -> ProgramResult { - let seed = ZointsTreasury::verify_program_key(treasury_info.key, name, program_id)?; - let lamports = rent.minimum_balance(ZointsTreasury::LEN); - let space = ZointsTreasury::LEN as u64; - invoke_signed( - &system_instruction::create_account( - funder_info.key, - treasury_info.key, - lamports, - space, - program_id, - ), - &[funder_info.clone(), treasury_info.clone()], - &[&[b"zoints", name, &[seed]]], - )?; - - let zoints_treasury = Self { - authority: *authority_info.key, - }; - Self::pack(zoints_treasury, &mut treasury_info.data.borrow_mut()) - } -} - -impl Pack for ZointsTreasury { - const LEN: usize = 32; - fn unpack_from_slice(src: &[u8]) -> Result { - let src = array_ref![src, 0, ZointsTreasury::LEN]; - - let authority = Pubkey::new(src); - - Ok(ZointsTreasury { authority }) - } - - fn pack_into_slice(&self, dst: &mut [u8]) { - let authority_dst = array_mut_ref![dst, 0, ZointsTreasury::LEN]; - *authority_dst = self.authority.to_bytes(); - } } #[cfg(test)] @@ -355,4 +194,35 @@ mod tests { cast_error(TreasuryError::ZointsTreasuryNameInvalidCharacters) ); } + + #[test] + pub fn test_serialize_accounts() { + let settings = Settings { + token: Pubkey::new_unique(), + fee_recipient: Pubkey::new_unique(), + price_authority: Pubkey::new_unique(), + launch_fee_user: 9238478234, + launch_fee_zoints: 1239718515, + }; + let settings_data = settings.try_to_vec().unwrap(); + assert_eq!(settings, Settings::try_from_slice(&settings_data).unwrap()); + + let user_treasury = UserTreasury { + authority: Pubkey::new_unique(), + }; + let user_treasury_data = user_treasury.try_to_vec().unwrap(); + assert_eq!( + user_treasury, + UserTreasury::try_from_slice(&user_treasury_data).unwrap() + ); + + let zoints_treasury = ZointsTreasury { + authority: Pubkey::new_unique(), + }; + let zoints_treasury_data = zoints_treasury.try_to_vec().unwrap(); + assert_eq!( + zoints_treasury, + ZointsTreasury::try_from_slice(&zoints_treasury_data).unwrap() + ); + } } diff --git a/program/src/error.rs b/program/src/error.rs index 0db3711..27d5313 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -1,5 +1,7 @@ use num_derive::FromPrimitive; -use solana_program::{decode_error::DecodeError, program_error::ProgramError}; +use solana_program::{ + decode_error::DecodeError, msg, program_error::PrintProgramError, program_error::ProgramError, +}; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Error, FromPrimitive)] @@ -84,3 +86,9 @@ impl DecodeError for TreasuryError { "TreasuryError" } } + +impl PrintProgramError for TreasuryError { + fn print(&self) { + msg!("TREASURY-ERROR: {}", &self.to_string()); + } +} diff --git a/program/src/instruction.rs b/program/src/instruction.rs index 3606676..c3b7727 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -1,12 +1,7 @@ -use crate::error::TreasuryError; -use arrayref::array_ref; -use solana_program::msg; -use solana_program::program_error::ProgramError; -use std::convert::TryInto; -use std::mem::size_of; +use borsh::{BorshDeserialize, BorshSerialize}; #[repr(C)] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)] pub enum TreasuryInstruction { /// Initialize the Treasury Program /// @@ -64,114 +59,40 @@ pub enum TreasuryInstruction { UpdateFees { fee_user: u64, fee_zoints: u64 }, } -impl TreasuryInstruction { - pub fn unpack(input: &[u8]) -> Result { - use TreasuryError::InvalidInstruction; +#[cfg(test)] +mod tests { + use super::*; + #[test] + pub fn test_serialize_instruction_init() { + let data = vec![ + 0, 0x5F, 0xCA, 0x12, 0, 0, 0, 0, 0, 0x96, 0xAD, 0x1D, 0x14, 0x2, 0, 0, 0, + ]; - let (&ins, rest) = input.split_first().ok_or(InvalidInstruction)?; + let instruction = TreasuryInstruction::Initialize { + fee_user: 1231455, + fee_zoints: 8927423894, + }; - Ok(match ins { - 0 => { - let (fee_user, rest) = rest.split_at(8); - let fee_user = fee_user - .try_into() - .ok() - .map(u64::from_le_bytes) - .ok_or(InvalidInstruction)?; - - let (fee_zoints, _rest) = rest.split_at(8); - let fee_zoints = fee_zoints - .try_into() - .ok() - .map(u64::from_le_bytes) - .ok_or(InvalidInstruction)?; - - TreasuryInstruction::Initialize { - fee_user, - fee_zoints, - } - } - 1 => TreasuryInstruction::CreateUserTreasury, - 2 => { - let (name, _rest) = unpack_vec(rest)?; - TreasuryInstruction::CreateZointsTreasury { name } - } - 3 => { - let (fee_user, rest) = rest.split_at(8); - let fee_user = fee_user - .try_into() - .ok() - .map(u64::from_le_bytes) - .ok_or(InvalidInstruction)?; - - let (fee_zoints, _rest) = rest.split_at(8); - let fee_zoints = fee_zoints - .try_into() - .ok() - .map(u64::from_le_bytes) - .ok_or(InvalidInstruction)?; - - TreasuryInstruction::UpdateFees { - fee_user, - fee_zoints, - } - } - _ => return Err(InvalidInstruction.into()), - }) + let serialized = instruction.try_to_vec().unwrap(); + assert_eq!(data, serialized); + let decoded = TreasuryInstruction::try_from_slice(&serialized).unwrap(); + assert_eq!(instruction, decoded); } - pub fn pack(&self) -> Vec { - let mut buf = Vec::with_capacity(size_of::()); - match self { - &TreasuryInstruction::Initialize { - fee_user, - fee_zoints, - } => { - buf.push(0); - buf.extend_from_slice(&fee_user.to_le_bytes()); - buf.extend_from_slice(&fee_zoints.to_le_bytes()); - } - TreasuryInstruction::CreateUserTreasury => buf.push(1), - TreasuryInstruction::CreateZointsTreasury { name } => { - buf.push(2); - pack_vec(&name, &mut buf); - } - TreasuryInstruction::UpdateFees { - fee_user, - fee_zoints, - } => { - buf.push(3); - buf.extend_from_slice(&fee_user.to_le_bytes()); - buf.extend_from_slice(&fee_zoints.to_le_bytes()); - } - } - buf - } -} + #[test] + pub fn test_serialize_instruction_create() { + let data = vec![ + 0x2, 0x17, 0, 0, 0, 0x61, 0x20, 0x72, 0x61, 0x6E, 0x64, 0x6F, 0x6D, 0x20, 0x75, 0x6E, + 0x69, 0x74, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x61, 0x6D, 0x65, + ]; -pub fn unpack_vec(input: &[u8]) -> Result<(Vec, &[u8]), ProgramError> { - if input.len() < 2 { - msg!("no len data for vector"); - return Err(ProgramError::InvalidInstructionData); - } - let (len, rest) = input.split_at(2); - let len = array_ref![len, 0, 2]; - let len = u16::from_le_bytes(*len) as usize; + let instruction = TreasuryInstruction::CreateZointsTreasury { + name: "a random unit test name".as_bytes().to_vec(), + }; - if rest.len() < len { - msg!( - "data too short for len. len = {}, actual = {}", - len, - rest.len() - ); - return Err(ProgramError::InvalidInstructionData); + let serialized = instruction.try_to_vec().unwrap(); + assert_eq!(data, serialized); + let decoded = TreasuryInstruction::try_from_slice(&serialized).unwrap(); + assert_eq!(instruction, decoded); } - let (data, rest) = rest.split_at(len); - - Ok((Vec::from(data), rest)) -} - -pub fn pack_vec(value: &Vec, buf: &mut Vec) { - buf.extend_from_slice(&(value.len() as u16).to_le_bytes()); - buf.extend_from_slice(&value); } diff --git a/program/src/processor.rs b/program/src/processor.rs index a35957b..c19b662 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -1,10 +1,12 @@ +use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, msg, - program::invoke, + program::{invoke, invoke_signed}, program_pack::Pack, pubkey::Pubkey, + system_instruction, sysvar::{rent::Rent, Sysvar}, }; @@ -19,29 +21,26 @@ use crate::{ pub struct Processor {} impl Processor { pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { - match TreasuryInstruction::unpack(input)? { + let instruction = TreasuryInstruction::try_from_slice(input) + .map_err(|_| TreasuryError::InvalidInstruction)?; + + msg!("Instruction :: {:?}", instruction); + + match instruction { TreasuryInstruction::Initialize { fee_user, fee_zoints, - } => { - msg!("Instruction :: Initialize"); - Self::process_initialize(program_id, accounts, fee_user, fee_zoints) - } + } => Self::process_initialize(program_id, accounts, fee_user, fee_zoints), TreasuryInstruction::CreateUserTreasury => { - msg!("Instruction :: CreateUserTreasury"); Self::process_create_user_treasury(program_id, accounts) } TreasuryInstruction::CreateZointsTreasury { name } => { - msg!("Instruction :: CreateZointsTreasury"); Self::process_create_zoints_treasury(program_id, accounts, name) } TreasuryInstruction::UpdateFees { fee_user, fee_zoints, - } => { - msg!("Instruction :: UpdateFees"); - Self::process_update_fees(program_id, accounts, fee_user, fee_zoints) - } + } => Self::process_update_fees(program_id, accounts, fee_user, fee_zoints), } } @@ -77,9 +76,6 @@ impl Processor { return Err(TreasuryError::AssociatedAccountWrongMint.into()); } - // verifies correctness of settings_info - Settings::create_account(funder_info, settings_info, rent, program_id)?; - let settings = Settings { token: *token_info.key, fee_recipient: *fee_recipient_info.key, @@ -88,7 +84,24 @@ impl Processor { launch_fee_zoints, }; - Settings::pack(settings, &mut settings_info.data.borrow_mut())?; + let data = settings.try_to_vec()?; + + let seed = Settings::verify_program_key(settings_info.key, program_id)?; + let lamports = rent.minimum_balance(data.len()); + let space = data.len() as u64; + invoke_signed( + &system_instruction::create_account( + funder_info.key, + settings_info.key, + lamports, + space, + program_id, + ), + &[funder_info.clone(), settings_info.clone()], + &[&[b"settings", &[seed]]], + )?; + + settings_info.data.borrow_mut().copy_from_slice(&data); Ok(()) } @@ -109,7 +122,7 @@ impl Processor { return Err(TreasuryError::NotInitialized.into()); } - let mut settings = Settings::unpack_unchecked(&settings_info.data.borrow())?; + let mut settings = Settings::try_from_slice(&settings_info.data.borrow())?; settings.verify_price_authority(authority_info)?; let fee_recipient = Account::unpack(&fee_recipient_info.data.borrow()) @@ -122,7 +135,10 @@ impl Processor { settings.launch_fee_user = fee_user; settings.launch_fee_zoints = fee_zoints; - Settings::pack(settings, &mut settings_info.data.borrow_mut())?; + settings_info + .data + .borrow_mut() + .copy_from_slice(&settings.try_to_vec()?); Ok(()) } @@ -155,7 +171,7 @@ impl Processor { return Err(TreasuryError::MissingCreatorSignature.into()); } - let settings = Settings::unpack_unchecked(&settings_info.data.borrow())?; + let settings = Settings::try_from_slice(&settings_info.data.borrow())?; if settings.token != *mint_info.key { return Err(TreasuryError::MintWrongToken.into()); } @@ -168,7 +184,28 @@ impl Processor { settings.launch_fee_user, )?; - UserTreasury::create_account(funder_info, treasury_info, creator_info, rent, program_id)?; + let user_treasury = UserTreasury { + authority: *creator_info.key, + }; + let data = user_treasury.try_to_vec()?; + + let seed = + UserTreasury::verify_program_key(treasury_info.key, creator_info.key, program_id)?; + let lamports = rent.minimum_balance(data.len()); + let space = data.len() as u64; + invoke_signed( + &system_instruction::create_account( + funder_info.key, + treasury_info.key, + lamports, + space, + program_id, + ), + &[funder_info.clone(), treasury_info.clone()], + &[&[b"user", &creator_info.key.to_bytes(), &[seed]]], + )?; + + treasury_info.data.borrow_mut().copy_from_slice(&data); invoke( &spl_token::instruction::transfer( @@ -219,7 +256,7 @@ impl Processor { return Err(TreasuryError::MissingCreatorSignature.into()); } - let settings = Settings::unpack_unchecked(&settings_info.data.borrow())?; + let settings = Settings::try_from_slice(&settings_info.data.borrow())?; if settings.token != *mint_info.key { return Err(TreasuryError::MintWrongToken.into()); } @@ -232,15 +269,28 @@ impl Processor { settings.launch_fee_user, )?; - ZointsTreasury::create_account( - funder_info, - treasury_info, - &name, - creator_info, - rent, - program_id, + let zoints_treasury = ZointsTreasury { + authority: *creator_info.key, + }; + let data = zoints_treasury.try_to_vec()?; + + let seed = ZointsTreasury::verify_program_key(treasury_info.key, &name, program_id)?; + let lamports = rent.minimum_balance(data.len()); + let space = data.len() as u64; + invoke_signed( + &system_instruction::create_account( + funder_info.key, + treasury_info.key, + lamports, + space, + program_id, + ), + &[funder_info.clone(), treasury_info.clone()], + &[&[b"zoints", &name, &[seed]]], )?; + treasury_info.data.borrow_mut().copy_from_slice(&data); + invoke( &spl_token::instruction::transfer( &spl_token::id(), diff --git a/test/index.ts b/test/index.ts index 13ed437..4388544 100644 --- a/test/index.ts +++ b/test/index.ts @@ -20,7 +20,6 @@ import { SystemProgram } from '@solana/web3.js'; import { Connection } from '@solana/web3.js'; -import { BADNAME } from 'dns'; import * as fs from 'fs'; const connection = new Connection('http://localhost:8899'); @@ -390,9 +389,9 @@ async function launch_zoints_treasury( isWritable: false } ]; - const prefix = Buffer.alloc(1 + 2, 0); + const prefix = Buffer.alloc(1 + 4, 0); prefix[0] = 2; - prefix.writeUInt16LE(name.length, 1); + prefix.writeUInt32LE(name.length, 1); const data = Buffer.concat([prefix, name]); const t = new Transaction()