refactor init/add oracle/remove oracle

This commit is contained in:
De Facto 2021-02-03 17:59:21 +08:00
parent d45b362dc6
commit c3c5028ff1
5 changed files with 330 additions and 853 deletions

View File

@ -0,0 +1,77 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
program_error::ProgramError,
program_pack::IsInitialized,
account_info::AccountInfo,
entrypoint::ProgramResult,
sysvar::rent::Rent,
msg,
};
pub trait BorshState: BorshDeserialize + BorshSerialize {
fn load(account: &AccountInfo) -> Result<Self, ProgramError> {
let data = (*account.data).borrow();
Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidAccountData)
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
let data = self.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
// FIXME: looks like there is association precedence issue that prevents
// RefMut from being automatically dereferenced.
//
// let dst = &mut account.data.borrow_mut();
//
// Why does it work in an SPL token program though?
//
// Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
let mut dst = (*account.data).borrow_mut();
if dst.len() != data.len() {
return Err(ProgramError::InvalidAccountData);
}
dst.copy_from_slice(&data);
Ok(())
}
fn save_exempt(&self, account: &AccountInfo, rent: &Rent) -> ProgramResult {
let data = self.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
if !rent.is_exempt(account.lamports(), data.len()) {
// FIXME: return a custom error
return Err(ProgramError::InvalidAccountData);
}
let mut dst = (*account.data).borrow_mut();
if dst.len() != data.len() {
// FIXME: return a custom error
return Err(ProgramError::InvalidAccountData);
}
dst.copy_from_slice(&data);
Ok(())
}
}
pub trait InitBorshState: BorshState + IsInitialized {
// type Self = IsInitialized
fn load_initialized(account: &AccountInfo) -> Result<Self, ProgramError> {
let object = Self::load(account)?;
if !object.is_initialized() {
return Err(ProgramError::UninitializedAccount);
}
Ok(object)
}
fn init_uninitialized(account: &AccountInfo) -> Result<Self, ProgramError> {
let object = Self::load(account)?;
if object.is_initialized() {
return Err(ProgramError::AccountAlreadyInitialized);
}
Ok(object)
}
}

View File

@ -1,17 +1,8 @@
//! Instruction types
// use crate::error::Error;
#![allow(dead_code)]
use solana_program::{
instruction::{AccountMeta, Instruction as SolInstruction},
program_error::ProgramError,
program_pack::{Pack, Sealed},
pubkey::Pubkey,
sysvar,
};
use crate::state::{AggregatorConfig};
// use std::convert::TryInto;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
/// Maximum number of oracles
@ -24,47 +15,17 @@ pub const PAYMENT_AMOUNT: u64 = 10;
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
pub enum Instruction {
/// Initializes a new Aggregator
///
/// Accounts expected by this instruction:
///
/// 0. `[]` Rent sysvar
/// 1. `[writable]` The aggregator.
/// 2. `[signer]` The aggregator's authority.
Initialize {
/// The interval(seconds) of an oracle's each submission
submit_interval: u32,
/// min submission value
min_submission_value: u64,
/// max submission value
max_submission_value: u64,
/// submission decimals
submission_decimals: u8,
/// A short description of what is being reported
description: [u8; 32],
config: AggregatorConfig,
},
/// Add an oracle
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The oracle
/// 1. `[]` The oracle owner
/// 2. `[]` Clock sysvar
/// 3. `[writable]` The aggregator
/// 4. `[signer]` The aggregator owner
AddOracle {
/// Is usually the oracle name
description: [u8; 32],
},
/// Remove an oracle
///
/// Accounts expected by this instruction:
/// 0. `[writable]` The aggregator.
/// 1. `[signer]` The aggregator onwer.
RemoveOracle {
/// the oracle pubkey
pubkey: [u8; 32],
},
RemoveOracle,
/// Called by oracles when they have witnessed a need to update
///
@ -94,158 +55,3 @@ pub enum Instruction {
seed: [u8; 32],
},
}
// impl Sealed for Instruction {}
// impl Pack for Instruction {
// const LEN: usize = 54;
// fn pack_into_slice(&self, dst: &mut [u8]) {
// let data = self.pack_into_vec();
// dst[..data.len()].copy_from_slice(&data);
// }
// fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
// let mut mut_src: &[u8] = src;
// Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidInstructionData)
// }
// }
impl Instruction {
fn pack_into_vec(&self) -> Vec<u8> {
self.try_to_vec().expect("try_to_vec")
}
}
// /// Creates a `add_oracle` instruction
// pub fn add_oracle(
// program_id: &Pubkey,
// oracle_pubkey: &Pubkey,
// oracle_owner_pubkey: &Pubkey,
// aggregator_pubkey: &Pubkey,
// aggregator_owner_pubkey: &Pubkey,
// description: [u8; 32],
// ) -> SolInstruction {
// let accounts = vec![
// AccountMeta::new(*oracle_pubkey, false),
// AccountMeta::new(*oracle_owner_pubkey, false),
// AccountMeta::new_readonly(sysvar::clock::id(), false),
// AccountMeta::new(*aggregator_pubkey, false),
// AccountMeta::new_readonly(*aggregator_owner_pubkey, true),
// ];
// SolInstruction {
// program_id: *program_id,
// accounts,
// data: Instruction::AddOracle { description }.pack_into_vec(),
// }
// }
// /// Creates a `remove_oracle` instruction
// pub fn remove_oracle(
// program_id: &Pubkey,
// aggregator_pubkey: &Pubkey,
// aggregator_owner_pubkey: &Pubkey,
// pubkey: &Pubkey,
// ) -> SolInstruction {
// let accounts = vec![
// AccountMeta::new(*aggregator_pubkey, false),
// AccountMeta::new_readonly(*aggregator_owner_pubkey, true),
// ];
// SolInstruction {
// program_id: *program_id,
// accounts,
// data: Instruction::RemoveOracle {
// pubkey: pubkey.to_bytes(),
// }
// .pack_into_vec(),
// }
// }
// /// Creates a `submit` instruction
// pub fn submit(
// program_id: &Pubkey,
// aggregator_pubkey: &Pubkey,
// oracle_pubkey: &Pubkey,
// oracle_owner_pubkey: &Pubkey,
// submission: u64,
// ) -> SolInstruction {
// let accounts = vec![
// AccountMeta::new(*aggregator_pubkey, false),
// AccountMeta::new_readonly(sysvar::clock::id(), false),
// AccountMeta::new(*oracle_pubkey, false),
// AccountMeta::new_readonly(*oracle_owner_pubkey, true),
// ];
// SolInstruction {
// program_id: *program_id,
// accounts,
// data: Instruction::Submit { submission }.pack_into_vec(),
// }
// }
#[cfg(test)]
mod tests {
use super::*;
use solana_program::{
entrypoint::ProgramResult,
};
use crate::borsh_utils;
use hex;
// #[test]
// fn test_get_packed_len() {
// assert_eq!(
// Instruction::get_packed_len(),
// borsh_utils::get_packed_len::<Instruction>()
// )
// }
#[test]
fn test_serialize_bytes() -> ProgramResult {
// let test_instruction = Instruction::Initialize {
// submit_interval: 0x11221122,
// min_submission_value: 0xaabbaabbaabbaabb,
// max_submission_value: 0xccddccddccddccdd,
// submission_decimals: 6,
// description: [0xff; 32],
// };
// let bytes = test_instruction.try_to_vec()?;
// assert_eq!(
// "0022112211bbaabbaabbaabbaaddccddccddccddcc06ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
// hex::encode(bytes),
// );
Ok(())
}
#[test]
fn state_deserialize_invalid() -> ProgramResult {
// assert_eq!(
// Instruction::unpack_from_slice(&hex::decode("0022112211bbaabbaabbaabbaaddccddccddccddcc06ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")?),
// Ok(Instruction::Initialize {
// submit_interval: 0x11221122,
// min_submission_value: 0xaabbaabbaabbaabb,
// max_submission_value: 0xccddccddccddccdd,
// submission_decimals: 6,
// description: [0xff; 32],
// }),
// );
// assert_eq!(
// Instruction::unpack_from_slice(&[
// 4,
// 15, 39, 0, 0, 0, 0, 0, 0,
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
// ]),
// Ok(Instruction::Withdraw {
// amount: 9999u64,
// seed: [1u8; 32],
// }),
// );
Ok(())
}
}

View File

@ -9,6 +9,7 @@ pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;
pub mod borsh_state;
#[cfg(not(feature = "no-entrypoint"))]
pub mod entrypoint;

View File

@ -1,10 +1,6 @@
//! Program state processor
use crate::{
error::Error,
instruction::{Instruction, PAYMENT_AMOUNT},
state::{Aggregator, Oracle, BorshState},
};
use crate::{error::Error, instruction::{Instruction, PAYMENT_AMOUNT}, state::{Aggregator, AggregatorConfig, Oracle}};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -18,6 +14,8 @@ use solana_program::{
sysvar::{rent::Rent, Sysvar},
};
use crate::borsh_state::{InitBorshState, BorshState};
use borsh::BorshDeserialize;
struct Accounts<'a, 'b>(&'a [AccountInfo<'b>]);
@ -39,35 +37,83 @@ struct InitializeContext<'a> {
aggregator: &'a AccountInfo<'a>,
owner: &'a AccountInfo<'a>,
submit_interval: u32,
min_submission_value: u64,
max_submission_value: u64,
submission_decimals: u8,
description: [u8; 32],
config: AggregatorConfig,
}
impl <'a>InitializeContext<'a> {
fn process(&self) -> ProgramResult {
if !self.owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let mut aggregator = Aggregator::load(self.aggregator)?;
let mut aggregator = Aggregator::init_uninitialized(self.aggregator)?;
aggregator.is_initialized = true;
aggregator.config = self.config.clone();
aggregator.owner = self.owner.key.to_bytes();
aggregator.save_exempt(self.aggregator, &self.rent)?;
if aggregator.is_initialized {
return Err(Error::AlreadyInUse)?;
Ok(())
}
}
struct AddOracleContext<'a> {
rent: Rent,
aggregator: &'a AccountInfo<'a>,
aggregator_owner: &'a AccountInfo<'a>, // signed
oracle: &'a AccountInfo<'a>,
oracle_owner: &'a AccountInfo<'a>,
description: [u8; 32],
}
impl <'a>AddOracleContext<'a> {
fn process(&self) -> ProgramResult {
// Note: there can in fact be more oracles than max_submissions
if !self.aggregator_owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
aggregator.submit_interval = self.submit_interval;
aggregator.min_submission_value = self.min_submission_value;
aggregator.max_submission_value = self.max_submission_value;
aggregator.submission_decimals = self.submission_decimals;
aggregator.description = self.description;
aggregator.is_initialized = true;
aggregator.owner = self.owner.key.to_bytes();
let aggregator = Aggregator::load_initialized(self.aggregator)?;
if aggregator.owner != self.aggregator_owner.key.to_bytes() {
return Err(Error::OwnerMismatch)?;
}
aggregator.save_exempt(self.aggregator, &self.rent)?;
let mut oracle = Oracle::init_uninitialized(self.oracle)?;
oracle.is_initialized = true;
oracle.description = self.description;
oracle.owner = self.oracle_owner.key.to_bytes();
oracle.aggregator = self.aggregator.key.to_bytes();
oracle.save_exempt(self.oracle, &self.rent)?;
Ok(())
}
}
struct RemoveOracleContext<'a> {
aggregator: &'a AccountInfo<'a>,
aggregator_owner: &'a AccountInfo<'a>, // signed
oracle: &'a AccountInfo<'a>,
}
impl <'a>RemoveOracleContext<'a> {
fn process(&self) -> ProgramResult {
if !self.aggregator_owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let aggregator = Aggregator::load_initialized(self.aggregator)?;
if aggregator.owner != self.aggregator_owner.key.to_bytes() {
return Err(Error::OwnerMismatch)?;
}
let oracle = Oracle::load_initialized(self.oracle)?;
if oracle.aggregator != self.aggregator.key.to_bytes() {
return Err(Error::OwnerMismatch)?;
}
// Zero out the oracle account memory. This allows reuse or reclaim.
// Note: will wipe out withdrawable balance on this oracle. Too bad.
Oracle::default().save(self.oracle)?;
Ok(())
}
@ -83,36 +129,36 @@ impl Processor {
match instruction {
Instruction::Initialize {
submit_interval,
min_submission_value,
max_submission_value,
submission_decimals,
description,
config,
} => {
InitializeContext {
rent: accounts.get_rent(0)?,
aggregator: accounts.get(1)?,
owner: accounts.get(2)?,
config,
}.process()
},
Instruction::AddOracle { description } => {
AddOracleContext {
rent: accounts.get_rent(0)?,
aggregator: accounts.get(1)?,
aggregator_owner: accounts.get(2)?,
oracle: accounts.get(3)?,
oracle_owner: accounts.get(4)?,
submit_interval,
min_submission_value,
max_submission_value,
submission_decimals,
description,
}.process()
}
},
Instruction::RemoveOracle => {
RemoveOracleContext {
aggregator: accounts.get(0)?,
aggregator_owner: accounts.get(1)?,
oracle: accounts.get(2)?,
}.process()
},
_ => {
Ok(())
}
// Instruction::AddOracle { description } => {
// msg!("Instruction: AddOracle");
// Self::process_add_oracle(accounts, description)
// }
// Instruction::RemoveOracle { pubkey } => {
// msg!("Instruction: RemoveOracle");
// Self::process_remove_oracle(accounts, pubkey)
// }
// Instruction::Submit { submission } => {
// msg!("Instruction: Submit");
// Self::process_submit(accounts, submission)
@ -124,150 +170,6 @@ impl Processor {
}
}
// Processes an [Initialize](enum.Instruction.html) instruction.
// pub fn process_initialize(
// _program_id: &Pubkey,
// accounts: &[AccountInfo],
// submit_interval: u32,
// min_submission_value: u64,
// max_submission_value: u64,
// submission_decimals: u8,
// description: [u8; 32],
// ) -> ProgramResult {
// let account_info_iter = &mut accounts.iter();
// let rent_info = next_account_info(account_info_iter)?;
// let aggregator_info = next_account_info(account_info_iter)?;
// let owner_info = next_account_info(account_info_iter)?;
// // check signer
// if !owner_info.is_signer {
// return Err(ProgramError::MissingRequiredSignature);
// }
// let rent = &Rent::from_account_info(rent_info)?;
// let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
// if aggregator.is_initialized {
// return Err(Error::AlreadyInUse.into());
// }
// if !rent.is_exempt(aggregator_info.lamports(), aggregator_info.data_len()) {
// return Err(Error::NotRentExempt.into());
// }
// aggregator.submit_interval = submit_interval;
// aggregator.min_submission_value = min_submission_value;
// aggregator.max_submission_value = max_submission_value;
// aggregator.submission_decimals = submission_decimals;
// aggregator.description = description;
// aggregator.is_initialized = true;
// aggregator.owner = owner_info.key.to_bytes();
// Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
// Ok(())
// }
// /// Processes an [AddOracle](enum.Instruction.html) instruction.
// pub fn process_add_oracle(accounts: &[AccountInfo], description: [u8; 32]) -> ProgramResult {
// let account_info_iter = &mut accounts.iter();
// let oracle_info = next_account_info(account_info_iter)?;
// let oracle_owner_info = next_account_info(account_info_iter)?;
// let clock_sysvar_info = next_account_info(account_info_iter)?;
// let aggregator_info = next_account_info(account_info_iter)?;
// let aggregator_owner_info = next_account_info(account_info_iter)?;
// if !aggregator_owner_info.is_signer {
// return Err(ProgramError::MissingRequiredSignature);
// }
// let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
// if !aggregator.is_initialized {
// return Err(Error::NotFoundAggregator.into());
// }
// if &Pubkey::new_from_array(aggregator.owner) != aggregator_owner_info.key {
// return Err(Error::OwnerMismatch.into());
// }
// let mut oracle = Oracle::unpack_unchecked(&oracle_info.data.borrow())?;
// if oracle.is_initialized {
// return Err(Error::AlreadyInUse.into());
// }
// // sys clock
// let clock = &Clock::from_account_info(clock_sysvar_info)?;
// let mut inserted = false;
// for s in aggregator.submissions.iter_mut() {
// if Pubkey::new_from_array(s.oracle) == Pubkey::default() {
// inserted = true;
// s.oracle = oracle_info.key.to_bytes();
// break;
// } else if &Pubkey::new_from_array(s.oracle) == oracle_info.key {
// return Err(Error::OracleExist.into());
// }
// }
// if !inserted {
// return Err(Error::MaxOralcesReached.into());
// }
// Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
// oracle.next_submit_time = clock.unix_timestamp;
// oracle.description = description;
// oracle.is_initialized = true;
// oracle.withdrawable = 0;
// oracle.aggregator = aggregator_info.key.to_bytes();
// oracle.owner = oracle_owner_info.key.to_bytes();
// Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
// Ok(())
// }
// /// Processes an [RemoveOracle](enum.Instruction.html) instruction.
// pub fn process_remove_oracle(accounts: &[AccountInfo], pubkey: [u8; 32]) -> ProgramResult {
// let account_info_iter = &mut accounts.iter();
// let aggregator_info = next_account_info(account_info_iter)?;
// let owner_info = next_account_info(account_info_iter)?;
// if !owner_info.is_signer {
// return Err(ProgramError::MissingRequiredSignature);
// }
// let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
// if !aggregator.is_initialized {
// return Err(Error::NotFoundAggregator.into());
// }
// if &Pubkey::new_from_array(aggregator.owner) != owner_info.key {
// return Err(Error::OwnerMismatch.into());
// }
// let mut found = false;
// for s in aggregator.submissions.iter_mut() {
// if s.oracle != Pubkey::default().to_bytes() && s.oracle == pubkey {
// found = true;
// s.oracle = Pubkey::default().to_bytes();
// break;
// }
// }
// if !found {
// return Err(Error::NotFoundOracle.into());
// }
// Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
// Ok(())
// }
// /// Processes an [Submit](enum.Instruction.html) instruction.
// pub fn process_submit(accounts: &[AccountInfo], submission: u64) -> ProgramResult {
// let account_info_iter = &mut accounts.iter();
@ -408,379 +310,165 @@ mod tests {
use super::*;
use borsh::BorshSerialize;
// use crate::{instruction::*, state::Submission};
use crate::borsh_utils;
use crate::instruction;
use solana_program::{instruction::Instruction, sysvar};
use solana_program::{sysvar};
use solana_sdk::account::{
create_account, Account,
// create_is_signer_account_infos
};
// use solana_program::account_info::
// pub fn create_is_signer_account_infos<'a>(
// mut accounts: Vec<(Pubkey, Account, bool)>,
// ) -> Vec<AccountInfo<'a>> {
pub fn create_is_signer_account_infos<'a>(
accounts: &'a mut [(Pubkey, Account, bool)],
) -> Vec<AccountInfo<'a>> {
accounts
.iter_mut()
.map(|(key, account, is_signer)| {
AccountInfo::new(
key,
*is_signer,
false,
&mut account.lamports,
&mut account.data,
&account.owner,
account.executable,
account.rent_epoch,
)
})
.collect()
}
fn process(
// instruction: Instruction,
// accounts: Vec<&mut SolanaAccount>,
fn process<'a>(
program_id: &Pubkey,
input: &[u8],
mut accounts: Vec<(Pubkey, Account, bool)>,
ix: instruction::Instruction,
accounts: &'a [AccountInfo<'a>],
) -> ProgramResult {
// let mut meta = accounts
// .iter_mut()
// .map(|(pubkey, account, signer)| (&pubkey, *signer, account))
// .collect::<Vec<_>>();
// let account_infos = create_is_signer_account_infos(meta.as_mut_slice());
let account_infos = create_is_signer_account_infos(&mut accounts);
Processor::process(&program_id, &account_infos, input)
let input = ix.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
Processor::process(&program_id, accounts, &input)
}
fn rent_sysvar() -> Account {
create_account(&Rent::default(), 42)
fn rent_sysvar() -> TSysAccount {
TSysAccount(sysvar::rent::id(), create_account(&Rent::default(), 42))
}
fn clock_sysvar() -> Account {
create_account(&Clock::default(), 42)
}
fn aggregator_minimum_balance() -> u64 {
Rent::default().minimum_balance(borsh_utils::get_packed_len::<Aggregator>())
fn rent_exempt_balance(space: usize) -> u64 {
Rent::default().minimum_balance(space)
}
// fn oracle_minimum_balance() -> u64 {
// Rent::default().minimum_balance(Oracle::get_packed_len())
// }
struct TAccount {
is_signer: bool,
pubkey: Pubkey,
account: Account,
}
// #[test]
// fn test_pack_unpack() {
// let check = Submission {
// time: 1,
// value: 1,
// oracle: [1; 32],
// };
impl TAccount {
fn new(program_id: &Pubkey, is_signer: bool) -> TAccount {
TAccount {
is_signer,
pubkey: Pubkey::new_unique(),
account: Account::new(0, 0, &program_id)
}
}
// let mut packed = vec![0; Submission::get_packed_len() + 1];
fn new_rent_exempt(program_id: &Pubkey, space: usize, is_signer: bool) -> TAccount {
TAccount {
is_signer,
pubkey: Pubkey::new_unique(),
account: Account::new(rent_exempt_balance(space), space, &program_id)
}
}
// assert_eq!(
// Err(ProgramError::InvalidAccountData),
// Submission::pack(check, &mut packed)
// );
// }
fn info(&mut self) -> AccountInfo {
AccountInfo::new(
&self.pubkey,
self.is_signer,
false,
&mut self.account.lamports,
&mut self.account.data,
&self.account.owner,
self.account.executable,
self.account.rent_epoch,
)
}
}
use crate::borsh_utils;
impl<'a> Into<AccountInfo<'a>> for &'a mut TAccount {
fn into(self) -> AccountInfo<'a> {
self.info()
}
}
struct TSysAccount (Pubkey, Account);
impl<'a> Into<AccountInfo<'a>> for &'a mut TSysAccount {
fn into(self) -> AccountInfo<'a> {
AccountInfo::new(
&self.0,
false,
false,
&mut self.1.lamports,
&mut self.1.data,
&self.1.owner,
self.1.executable,
self.1.rent_epoch,
)
}
}
fn setup_aggregator(program_id: &Pubkey) -> Result<(TAccount, TAccount), ProgramError> {
let mut rent_sysvar = rent_sysvar();
let mut aggregator = TAccount::new_rent_exempt(&program_id, borsh_utils::get_packed_len::<Aggregator>(), false);
let mut aggregator_owner = TAccount::new(&program_id, true);
process(
&program_id,
instruction::Instruction::Initialize {
config: AggregatorConfig{
submit_interval: 10,
min_submission_value: 0,
max_submission_value: 100,
submission_decimals: 8,
description: [0u8; 32],
}
},
vec![
(&mut rent_sysvar).into(),
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
].as_slice(),
)?;
Ok((aggregator, aggregator_owner))
}
#[test]
fn test_intialize() -> ProgramResult {
let program_id = Pubkey::new_unique();
let aggregator_key = Pubkey::new_unique();
let owner_key = Pubkey::new_unique();
setup_aggregator(&program_id)?;
Ok(())
}
let inx = instruction::Instruction::Initialize {
submit_interval: 10,
min_submission_value: 0,
max_submission_value: 100,
submission_decimals: 8,
description: [0u8; 32],
};
#[test]
fn test_add_and_remove_oracle() -> ProgramResult {
let program_id = Pubkey::new_unique();
let data = inx.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
let (mut aggregator, mut aggregator_owner) = setup_aggregator(&program_id)?;
let rent_sysvar = rent_sysvar();
let aggregator_account = Account::new(aggregator_minimum_balance(), borsh_utils::get_packed_len::<Aggregator>(), &program_id);
let owner_account = Account::default();
// Ok(())
// aggregator is not rent exempt
// assert_eq!(
// Err(Error::NotRentExempt.into()),
// do_process_instruction(
// initialize(
// &program_id,
// &aggregator_key,
// &owner_key,
// 6,
// 1,
// 9999,
// 6,
// [1; 32]
// ),
// vec![
// &mut rent_sysvar,
// &mut aggregator_account,
// &mut owner_account,
// ]
// )
// );
// aggregator_account.lamports = aggregator_minimum_balance();
// initialize will be successful
let mut rent_sysvar = rent_sysvar();
let mut oracle = TAccount::new_rent_exempt(&program_id, borsh_utils::get_packed_len::<Oracle>(), false);
let mut oracle_owner = TAccount::new(&program_id, true);
process(
&program_id,
&data,
instruction::Instruction::AddOracle {
description: [0xab; 32],
},
vec![
(sysvar::rent::id(), rent_sysvar, false),
(aggregator_key, aggregator_account, false),
(owner_key, owner_account, true),
],
(&mut rent_sysvar).into(),
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
(&mut oracle).into(),
(&mut oracle_owner).into(),
].as_slice(),
)?;
process(
&program_id,
instruction::Instruction::RemoveOracle {},
vec![
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
(&mut oracle).into(),
].as_slice(),
)?;
// println!("{}", hex::encode(oracle.account.data));
Ok(())
// .unwrap();
// // duplicate initialize will get failed
// assert_eq!(
// Err(Error::AlreadyInUse.into()),
// do_process_instruction(
// initialize(
// &program_id,
// &aggregator_key,
// &owner_key,
// 6,
// 1,
// 9999,
// 6,
// [1; 32]
// ),
// vec![
// &mut rent_sysvar,
// &mut aggregator_account,
// &mut owner_account,
// ]
// )
// );
}
// #[test]
// fn test_add_oracle() {
// let program_id = Pubkey::new_unique();
// let oracle_key = Pubkey::new_unique();
// let oracle_owner_key = Pubkey::new_unique();
// let aggregator_key = Pubkey::new_unique();
// let aggregator_owner_key = Pubkey::new_unique();
// let mut rent_sysvar = rent_sysvar();
// let mut clock_sysvar = clock_sysvar();
// let mut oracle_account = SolanaAccount::new(
// oracle_minimum_balance(),
// Oracle::get_packed_len(),
// &program_id,
// );
// let mut aggregator_account = SolanaAccount::new(
// aggregator_minimum_balance(),
// Aggregator::get_packed_len(),
// &program_id,
// );
// let mut oracle_owner_account = SolanaAccount::default();
// let mut aggregator_owner_account = SolanaAccount::default();
// // add oracle to unexist aggregator
// assert_eq!(
// Err(Error::NotFoundAggregator.into()),
// do_process_instruction(
// add_oracle(
// &program_id,
// &oracle_key,
// &oracle_owner_key,
// &aggregator_key,
// &aggregator_owner_key,
// [1; 32]
// ),
// vec![
// &mut oracle_account,
// &mut oracle_owner_account,
// &mut clock_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ]
// )
// );
// // initialize aggregator
// do_process_instruction(
// initialize(
// &program_id,
// &aggregator_key,
// &aggregator_owner_key,
// 6,
// 1,
// 9999,
// 6,
// [1; 32],
// ),
// vec![
// &mut rent_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ],
// )
// .unwrap();
// // will be successful
// do_process_instruction(
// add_oracle(
// &program_id,
// &oracle_key,
// &oracle_owner_key,
// &aggregator_key,
// &aggregator_owner_key,
// [1; 32],
// ),
// vec![
// &mut oracle_account,
// &mut oracle_owner_account,
// &mut clock_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ],
// )
// .unwrap();
// // duplicate oracle
// assert_eq!(
// Err(Error::AlreadyInUse.into()),
// do_process_instruction(
// add_oracle(
// &program_id,
// &oracle_key,
// &oracle_owner_key,
// &aggregator_key,
// &aggregator_owner_key,
// [1; 32]
// ),
// vec![
// &mut oracle_account,
// &mut oracle_owner_account,
// &mut clock_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ]
// )
// );
// }
// #[test]
// fn test_remove_oracle() {
// let program_id = Pubkey::new_unique();
// let oracle_key = Pubkey::new_unique();
// let oracle_owner_key = Pubkey::new_unique();
// let aggregator_key = Pubkey::new_unique();
// let aggregator_owner_key = Pubkey::new_unique();
// let mut rent_sysvar = rent_sysvar();
// let mut clock_sysvar = clock_sysvar();
// let mut oracle_account = SolanaAccount::new(
// oracle_minimum_balance(),
// Oracle::get_packed_len(),
// &program_id,
// );
// let mut aggregator_account = SolanaAccount::new(
// aggregator_minimum_balance(),
// Aggregator::get_packed_len(),
// &program_id,
// );
// let mut oracle_owner_account = SolanaAccount::default();
// let mut aggregator_owner_account = SolanaAccount::default();
// // initialize aggregator
// do_process_instruction(
// initialize(
// &program_id,
// &aggregator_key,
// &aggregator_owner_key,
// 6,
// 1,
// 9999,
// 6,
// [1; 32],
// ),
// vec![
// &mut rent_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ],
// )
// .unwrap();
// // add oracle
// do_process_instruction(
// add_oracle(
// &program_id,
// &oracle_key,
// &oracle_owner_key,
// &aggregator_key,
// &aggregator_owner_key,
// [1; 32],
// ),
// vec![
// &mut oracle_account,
// &mut oracle_owner_account,
// &mut clock_sysvar,
// &mut aggregator_account,
// &mut aggregator_owner_account,
// ],
// )
// .unwrap();
// // remove an unexist oracle
// assert_eq!(
// Err(Error::NotFoundOracle.into()),
// do_process_instruction(
// remove_oracle(
// &program_id,
// &aggregator_key,
// &aggregator_owner_key,
// &Pubkey::default()
// ),
// vec![&mut aggregator_account, &mut aggregator_owner_account,]
// )
// );
// // will be successful
// do_process_instruction(
// remove_oracle(
// &program_id,
// &aggregator_key,
// &aggregator_owner_key,
// &oracle_key,
// ),
// vec![&mut aggregator_account, &mut aggregator_owner_account],
// )
// .unwrap();
// }
// #[test]
// fn test_submit() {
// let program_id = Pubkey::new_unique();

View File

@ -2,66 +2,15 @@
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use crate::instruction::MAX_ORACLES;
use crate::borsh_state::{InitBorshState, BorshState};
use solana_program::{
clock::UnixTimestamp,
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
account_info::AccountInfo,
entrypoint::ProgramResult,
sysvar::rent::Rent,
msg,
program_pack::IsInitialized,
};
pub trait BorshState: BorshDeserialize + BorshSerialize {
fn load(account: &AccountInfo) -> Result<Self, ProgramError> {
let data = (*account.data).borrow();
Self::try_from_slice(&data).map_err(|_| ProgramError::InvalidAccountData)
}
fn save(&self, account: &AccountInfo) -> ProgramResult {
let data = self.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
// FIXME: looks like there is association precedence issue that prevents
// RefMut from being automatically dereferenced.
//
// let dst = &mut account.data.borrow_mut();
//
// Why does it work in an SPL token program though?
//
// Account::pack(source_account, &mut source_account_info.data.borrow_mut())?;
let mut dst = (*account.data).borrow_mut();
if dst.len() != data.len() {
return Err(ProgramError::InvalidAccountData);
}
dst.copy_from_slice(&data);
Ok(())
}
fn save_exempt(&self, account: &AccountInfo, rent: &Rent) -> ProgramResult {
let data = self.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
if !rent.is_exempt(account.lamports(), data.len()) {
// FIXME: return a custom error
return Err(ProgramError::InvalidAccountData);
}
let mut dst = (*account.data).borrow_mut();
if dst.len() != data.len() {
// FIXME: return a custom error
return Err(ProgramError::InvalidAccountData);
}
dst.copy_from_slice(&data);
Ok(())
}
}
/// Aggregator data.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Aggregator {
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct AggregatorConfig {
/// The interval(seconds) of an oracle's each submission
pub submit_interval: u32,
/// min submission value
@ -72,15 +21,45 @@ pub struct Aggregator {
pub submission_decimals: u8,
/// description
pub description: [u8; 32],
}
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Round {
pub id: u64,
pub started_at: u64,
pub updated_at: u64,
pub submissions: [Submission; MAX_ORACLES],
}
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Answer {
pub round_id: u64,
pub created_at: u64,
pub updated_at: u64,
pub submissions: [Submission; MAX_ORACLES],
}
/// Aggregator data.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Aggregator {
pub config: AggregatorConfig,
/// is initialized
pub is_initialized: bool,
/// authority
pub owner: [u8; 32],
/// submissions
pub submissions: [Submission; MAX_ORACLES],
/// current round accepting oracle submissions
pub current_round: Round,
/// the latest answer resolved
pub answer: Answer,
}
impl IsInitialized for Aggregator {
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl BorshState for Aggregator {}
impl InitBorshState for Aggregator {}
/// Submission data.
#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
@ -97,8 +76,6 @@ pub struct Submission {
/// Oracle data.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Oracle {
/// submit time
pub next_submit_time: UnixTimestamp,
/// is usually the oracle name
pub description: [u8; 32],
/// is initialized
@ -110,82 +87,10 @@ pub struct Oracle {
/// owner
pub owner: [u8; 32],
}
impl BorshState for Oracle {}
impl IsInitialized for Oracle {
fn is_initialized(&self) -> bool {
self.is_initialized
}
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
// impl Sealed for Oracle {}
// impl Pack for Oracle {
// const LEN: usize = 113;
// fn pack_into_slice(&self, dst: &mut [u8]) {
// let data = self.try_to_vec().unwrap();
// dst[..data.len()].copy_from_slice(&data);
// }
// fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
// let mut mut_src: &[u8] = src;
// Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
// }
// }
// impl Sealed for Submission {}
// impl Pack for Submission {
// const LEN: usize = 48;
// fn pack_into_slice(&self, dst: &mut [u8]) {
// let data = self.try_to_vec().unwrap();
// dst[..data.len()].copy_from_slice(&data);
// }
// fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
// let mut mut_src: &[u8] = src;
// Self::deserialize(&mut mut_src).map_err(|_| ProgramError::InvalidAccountData)
// }
// }
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::borsh_utils;
// #[test]
// fn test_get_packed_len() {
// assert_eq!(
// Aggregator::get_packed_len(),
// borsh_utils::get_packed_len::<Aggregator>()
// );
// assert_eq!(
// Oracle::get_packed_len(),
// borsh_utils::get_packed_len::<Oracle>()
// );
// assert_eq!(
// Submission::get_packed_len(),
// borsh_utils::get_packed_len::<Submission>()
// );
// }
// #[test]
// fn test_serialize_bytes() {
// assert_eq!(
// Submission {
// time: 0,
// value: 1,
// oracle: [1; 32]
// }
// .try_to_vec()
// .unwrap(),
// vec![
// 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
// ]
// );
// }
// }
impl InitBorshState for Oracle {}