This commit is contained in:
De Facto 2021-02-04 09:50:04 +08:00
parent ebdeff24e6
commit 41d19e86b0
5 changed files with 207 additions and 166 deletions

View File

@ -1,77 +1,76 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
program_error::ProgramError,
program_pack::IsInitialized,
account_info::AccountInfo,
entrypoint::ProgramResult,
sysvar::rent::Rent,
msg,
account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError,
program_pack::IsInitialized, sysvar::rent::Rent,
};
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);
fn load(account: &AccountInfo) -> Result<Self, ProgramError> {
let data = (*account.data).borrow();
Self::try_from_slice(&data).map_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);
fn save(&self, account: &AccountInfo) -> ProgramResult {
let data = self
.try_to_vec()
.map_err(|_| ProgramError::InvalidAccountData)?;
Ok(())
}
// 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);
// 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)
}
Ok(object)
}
fn init_uninitialized(account: &AccountInfo) -> Result<Self, ProgramError> {
let object = Self::load(account)?;
if object.is_initialized() {
return Err(ProgramError::AccountAlreadyInitialized);
}
fn init_uninitialized(account: &AccountInfo) -> Result<Self, ProgramError> {
let object = Self::load(account)?;
if object.is_initialized() {
return Err(ProgramError::AccountAlreadyInitialized);
Ok(object)
}
Ok(object)
}
}
}

View File

@ -1,7 +1,7 @@
//! Instruction types
#![allow(dead_code)]
use crate::state::{AggregatorConfig};
use crate::state::AggregatorConfig;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};

View File

@ -4,12 +4,12 @@
use solana_program::{account_info::AccountInfo, program_error::ProgramError, program_pack::Pack};
pub mod borsh_state;
pub mod borsh_utils;
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

@ -2,7 +2,11 @@
use std::default;
use crate::{error::Error, instruction::{Instruction, PAYMENT_AMOUNT}, state::{Aggregator, AggregatorConfig, Oracle, Round}};
use crate::{
error::Error,
instruction::{Instruction, PAYMENT_AMOUNT},
state::{Aggregator, AggregatorConfig, Oracle, Round},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -11,21 +15,21 @@ use solana_program::{
msg,
program::invoke_signed,
program_error::ProgramError,
program_pack::{IsInitialized},
program_pack::IsInitialized,
pubkey::Pubkey,
sysvar::{rent::Rent, Sysvar},
};
use crate::borsh_state::{InitBorshState, BorshState};
use crate::borsh_state::{BorshState, InitBorshState};
use borsh::BorshDeserialize;
struct Accounts<'a, 'b>(&'a [AccountInfo<'b>]);
impl <'a, 'b>Accounts<'a, 'b> {
impl<'a, 'b> Accounts<'a, 'b> {
fn get(&self, i: usize) -> Result<&'a AccountInfo<'b>, ProgramError> {
// fn get(&self, i: usize) -> Result<&AccountInfo, ProgramError> {
// &accounts[input.token.account as usize]
// fn get(&self, i: usize) -> Result<&AccountInfo, ProgramError> {
// &accounts[input.token.account as usize]
self.0.get(i).ok_or(ProgramError::NotEnoughAccountKeys)
}
@ -46,7 +50,7 @@ struct InitializeContext<'a> {
config: AggregatorConfig,
}
impl <'a>InitializeContext<'a> {
impl<'a> InitializeContext<'a> {
fn process(&self) -> ProgramResult {
if !self.owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
@ -72,7 +76,7 @@ struct AddOracleContext<'a> {
description: [u8; 32],
}
impl <'a>AddOracleContext<'a> {
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 {
@ -101,7 +105,7 @@ struct RemoveOracleContext<'a> {
oracle: &'a AccountInfo<'a>,
}
impl <'a>RemoveOracleContext<'a> {
impl<'a> RemoveOracleContext<'a> {
fn process(&self) -> ProgramResult {
if !self.aggregator_owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
@ -136,7 +140,7 @@ struct SubmitContext<'a> {
value: u64,
}
impl <'a>SubmitContext<'a> {
impl<'a> SubmitContext<'a> {
fn process(&self) -> ProgramResult {
let mut aggregator = Aggregator::load_initialized(self.aggregator)?;
let mut oracle = Oracle::load_initialized(self.oracle)?;
@ -176,11 +180,17 @@ impl <'a>SubmitContext<'a> {
fn submit(&self, aggregator: &mut Aggregator, oracle: &Oracle) -> ProgramResult {
let now = self.clock.unix_timestamp as u64;
let (i, submission) = aggregator.current_round.submissions.iter_mut().enumerate().find(|(i, s)| {
// either finds a new spot to put the submission, or find a spot
// that the oracle previously submitted to.
return !s.is_initialized() || s.oracle == oracle.owner;
}).ok_or(Error::MaxSubmissionsReached)?;
let (i, submission) = aggregator
.current_round
.submissions
.iter_mut()
.enumerate()
.find(|(i, s)| {
// either finds a new spot to put the submission, or find a spot
// that the oracle previously submitted to.
return !s.is_initialized() || s.oracle == oracle.owner;
})
.ok_or(Error::MaxSubmissionsReached)?;
let count = i + 1;
@ -220,7 +230,7 @@ impl <'a>SubmitContext<'a> {
Ok(())
}
fn start_new_round(&self, aggregator: &mut Aggregator, oracle: &mut Oracle) -> ProgramResult {
fn start_new_round(&self, aggregator: &mut Aggregator, oracle: &mut Oracle) -> ProgramResult {
let now = self.clock.unix_timestamp as u64;
if oracle.allow_start_round <= aggregator.current_round.id {
@ -245,61 +255,57 @@ impl <'a>SubmitContext<'a> {
pub struct Processor {}
impl Processor {
pub fn process<'a>(program_id: &Pubkey, accounts: &'a [AccountInfo<'a>], input: &[u8]) -> ProgramResult {
pub fn process<'a>(
program_id: &Pubkey,
accounts: &'a [AccountInfo<'a>],
input: &[u8],
) -> ProgramResult {
let accounts = Accounts(accounts);
let instruction = Instruction::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)?;
let instruction =
Instruction::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)?;
match instruction {
Instruction::Initialize {
Instruction::Initialize { config } => InitializeContext {
rent: accounts.get_rent(0)?,
aggregator: accounts.get(1)?,
owner: accounts.get(2)?,
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)?,
description,
}.process()
},
Instruction::RemoveOracle => {
RemoveOracleContext {
aggregator: accounts.get(0)?,
aggregator_owner: accounts.get(1)?,
oracle: accounts.get(2)?,
}.process()
},
Instruction::Submit { round_id, value } => {
SubmitContext {
clock: accounts.get_clock(0)?,
aggregator: accounts.get(1)?,
oracle: accounts.get(2)?,
oracle_owner: accounts.get(3)?,
round_id,
value
}.process()
},
_ => {
Err(ProgramError::InvalidInstructionData)
}
// Instruction::Submit { submission } => {
// msg!("Instruction: Submit");
// Self::process_submit(accounts, submission)
// }
// Instruction::Withdraw { amount, seed } => {
// msg!("Instruction: Withdraw");
// Self::process_withdraw(accounts, amount, seed)
// }
.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)?,
description,
}
.process(),
Instruction::RemoveOracle => RemoveOracleContext {
aggregator: accounts.get(0)?,
aggregator_owner: accounts.get(1)?,
oracle: accounts.get(2)?,
}
.process(),
Instruction::Submit { round_id, value } => SubmitContext {
clock: accounts.get_clock(0)?,
aggregator: accounts.get(1)?,
oracle: accounts.get(2)?,
oracle_owner: accounts.get(3)?,
round_id,
value,
}
.process(),
_ => Err(ProgramError::InvalidInstructionData), // Instruction::Submit { submission } => {
// msg!("Instruction: Submit");
// Self::process_submit(accounts, submission)
// }
// Instruction::Withdraw { amount, seed } => {
// msg!("Instruction: Withdraw");
// Self::process_withdraw(accounts, amount, seed)
// }
}
}
@ -373,21 +379,21 @@ impl Processor {
mod tests {
use super::*;
use borsh::BorshSerialize;
use crate::borsh_utils;
use crate::instruction;
use solana_program::{sysvar};
use borsh::BorshSerialize;
use solana_program::sysvar;
use solana_sdk::account::{
create_account, Account,
};
use solana_sdk::account::{create_account, Account};
fn process<'a>(
program_id: &Pubkey,
ix: instruction::Instruction,
accounts: &'a [AccountInfo<'a>],
) -> ProgramResult {
let input = ix.try_to_vec().map_err(|_| ProgramError::InvalidAccountData)?;
let input = ix
.try_to_vec()
.map_err(|_| ProgramError::InvalidAccountData)?;
Processor::process(&program_id, accounts, &input)
}
@ -414,7 +420,7 @@ mod tests {
TAccount {
is_signer,
pubkey: Pubkey::new_unique(),
account: Account::new(0, 0, &program_id)
account: Account::new(0, 0, &program_id),
}
}
@ -422,7 +428,7 @@ mod tests {
TAccount {
is_signer,
pubkey: Pubkey::new_unique(),
account: Account::new(rent_exempt_balance(space), space, &program_id)
account: Account::new(rent_exempt_balance(space), space, &program_id),
}
}
@ -446,7 +452,7 @@ mod tests {
}
}
struct TSysAccount (Pubkey, Account);
struct TSysAccount(Pubkey, Account);
impl<'a> Into<AccountInfo<'a>> for &'a mut TSysAccount {
fn into(self) -> AccountInfo<'a> {
AccountInfo::new(
@ -464,28 +470,61 @@ mod tests {
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 = 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{
config: AggregatorConfig {
decimals: 8,
description: [0u8; 32],
..AggregatorConfig::default()
}
},
},
vec![
(&mut rent_sysvar).into(),
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
].as_slice(),
]
.as_slice(),
)?;
Ok((aggregator, aggregator_owner))
}
// fn create_oracle(
// program_id: &Pubkey,
// aggregator: &mut TAccount,
// aggregator_owner: &mut TAccount,
// ) -> Result<(TAccount, TAccount), ProgramError> {
// 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,
// instruction::Instruction::AddOracle {
// description: [0xab; 32],
// },
// vec![
// (&mut rent_sysvar).into(),
// aggregator.into(),
// aggregator_owner.into(),
// (&mut oracle).into(),
// (&mut oracle_owner).into(),
// ]
// .as_slice(),
// )?;
// Ok((oracle, oracle_owner))
// }
#[test]
fn test_intialize() -> ProgramResult {
let program_id = Pubkey::new_unique();
@ -500,7 +539,8 @@ mod tests {
let (mut aggregator, mut aggregator_owner) = setup_aggregator(&program_id)?;
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 =
TAccount::new_rent_exempt(&program_id, borsh_utils::get_packed_len::<Oracle>(), false);
let mut oracle_owner = TAccount::new(&program_id, true);
process(
@ -514,7 +554,8 @@ mod tests {
(&mut aggregator_owner).into(),
(&mut oracle).into(),
(&mut oracle_owner).into(),
].as_slice(),
]
.as_slice(),
)?;
process(
@ -524,7 +565,8 @@ mod tests {
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
(&mut oracle).into(),
].as_slice(),
]
.as_slice(),
)?;
// println!("{}", hex::encode(oracle.account.data));

View File

@ -1,13 +1,13 @@
//! State transition types
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use crate::borsh_state::{BorshState, InitBorshState};
use crate::instruction::MAX_ORACLES;
use crate::borsh_state::{InitBorshState, BorshState};
use solana_program::{program_pack::IsInitialized};
use solana_program::program_pack::IsInitialized;
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct AggregatorConfig {
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct AggregatorConfig {
/// decimals for this feed
pub decimals: u8,
@ -25,22 +25,22 @@ use solana_program::{program_pack::IsInitialized};
/// min number of submissions in a round to resolve an answer
pub min_submissions: u8,
}
}
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
pub struct Round {
#[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 {
}
#[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)]
@ -57,9 +57,9 @@ pub struct Aggregator {
}
impl IsInitialized for Aggregator {
fn is_initialized(&self) -> bool {
self.is_initialized
}
fn is_initialized(&self) -> bool {
self.is_initialized
}
}
impl BorshState for Aggregator {}
@ -77,9 +77,9 @@ pub struct Submission {
}
impl IsInitialized for Submission {
fn is_initialized(&self) -> bool {
self.updated_at > 0
}
fn is_initialized(&self) -> bool {
self.updated_at > 0
}
}
/// Oracle data.
@ -102,8 +102,8 @@ pub struct Oracle {
}
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 InitBorshState for Oracle {}
impl InitBorshState for Oracle {}