store submissions in the aggregator account

This commit is contained in:
czl1378 2020-12-08 11:57:03 +08:00
parent 5525a5d506
commit afc7e766cf
6 changed files with 186 additions and 55 deletions

View File

@ -21,6 +21,7 @@ crate-type = ["cdylib", "lib"]
[features]
program = []
no-entrypoint = []
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -37,6 +37,9 @@ pub enum Error {
/// Insufficient withdrawable
#[error("Insufficient withdrawable")]
InsufficientWithdrawable,
/// Aggregator key not match
#[error("Aggregator key not match")]
AggregatorKeyNotMatch,
}
impl From<Error> for ProgramError {

View File

@ -5,14 +5,13 @@ use crate::error::Error;
use solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
info,
};
use std::convert::TryInto;
/// Maximum number of oracles
pub const MAX_ORACLES: usize = 21;
/// The interval(seconds) of an oracle's each submission
pub const SUBMIT_INTERVAL: i64 = 6;
/// The amount paid of TOKEN paid to each oracle per submission, in lamports (10e-10 SOL)
pub const PAYMENT_AMOUNT: u64 = 10;
@ -22,6 +21,8 @@ pub const PAYMENT_AMOUNT: u64 = 10;
pub enum Instruction {
/// Initializes a new Aggregator
Initialize {
/// The interval(seconds) of an oracle's each submission
submit_interval: u32,
/// min submission value
min_submission_value: u64,
/// max submission value
@ -69,31 +70,41 @@ impl Instruction {
let (&tag, rest) = input.split_first().ok_or(InvalidInstruction)?;
Ok(match tag {
0 => {
let (submit_interval, rest) = rest.split_at(4);
info!("1");
let submit_interval = submit_interval
.try_into()
.ok()
.map(u32::from_le_bytes)
.ok_or(InvalidInstruction)?;
info!("2");
let (min_submission_value, rest) = rest.split_at(8);
let min_submission_value = min_submission_value
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
info!("3");
let (max_submission_value, rest) = rest.split_at(8);
let max_submission_value = max_submission_value
.try_into()
.ok()
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
info!("4");
let (description, _rest) = rest.split_at(32);
let description = description
.try_into()
.ok()
.ok_or(InvalidInstruction)?;
info!("5");
Self::Initialize {
submit_interval,
min_submission_value,
max_submission_value,
description,
}
},
1 => {
let (description, _rest) = rest.split_at(32);

View File

@ -3,6 +3,12 @@
//! An Flux Aggregator program for the Solana blockchain
use solana_program::{
account_info::{AccountInfo},
program_error::{ProgramError},
program_pack::{Pack},
};
pub mod instruction;
pub mod processor;
pub mod error;
@ -11,5 +17,40 @@ pub mod state;
#[cfg(not(feature = "no-entrypoint"))]
pub mod entrypoint;
// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;
use error::Error;
use state::Aggregator;
/// Get median value from the aggregator account
pub fn get_submission_value(
aggregator_info: &AccountInfo
) -> Result<u64, ProgramError> {
let aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
}
let submissions = aggregator.submissions;
let mut values = vec![];
// if the submission value is 0, maybe the oracle is not initialized
for s in &submissions {
if s.value != 0 {
values.push(s.value);
}
}
// get median value
values.sort();
let l = values.len();
let i = l / 2;
if l % 2 == 0 {
return Ok((values[i] + values[i-1])/2);
} else {
return Ok(values[i]);
}
}
// Export current sdk types for downstream users building with a different
pub use solana_program;

View File

@ -2,8 +2,8 @@
use crate::{
error::Error,
instruction::{Instruction, SUBMIT_INTERVAL, PAYMENT_AMOUNT},
state::{Aggregator, Oracle},
instruction::{Instruction, PAYMENT_AMOUNT},
state::{Aggregator, Oracle, Submission},
};
use num_traits::FromPrimitive;
@ -30,13 +30,14 @@ impl Processor {
match instruction {
Instruction::Initialize {
submit_interval,
min_submission_value,
max_submission_value,
description,
} => {
info!("Instruction: Initialize");
Self::process_initialize(
program_id, accounts, min_submission_value,
program_id, accounts, submit_interval, min_submission_value,
max_submission_value, description,
)
},
@ -83,8 +84,9 @@ impl Processor {
/// 1. `[]` Sysvar rent
/// 2. `[writable, signer]` The aggregator autority
pub fn process_initialize(
program_id: &Pubkey,
_program_id: &Pubkey,
accounts: &[AccountInfo],
submit_interval: u32,
min_submission_value: u64,
max_submission_value: u64,
description: [u8; 32],
@ -111,6 +113,7 @@ impl Processor {
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.description = description;
@ -150,32 +153,37 @@ impl Processor {
return Err(Error::NotFoundAggregator.into());
}
let mut oracle = Oracle::unpack_unchecked(&oracle_info.data.borrow())?;
if oracle.is_initialized {
return Err(Error::AlreadyInUse.into());
}
let mut oracles = aggregator.oracles;
// sys clock
let clock = &Clock::from_account_info(clock_sysvar_info)?;
// append
for o in oracles.iter_mut() {
if o == &Pubkey::default() {
*o = *oracle_info.key;
let mut submissions = aggregator.submissions;
// default submission
for s in submissions.iter_mut() {
if s.oracle == Pubkey::default() {
*s = Submission {
time: clock.unix_timestamp,
value: 0,
oracle: *oracle_info.key,
};
break;
}
}
aggregator.oracles = oracles;
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
let clock = &Clock::from_account_info(clock_sysvar_info)?;
oracle.submission = 0;
oracle.next_submit_time = clock.unix_timestamp;
oracle.description = description;
oracle.is_initialized = true;
oracle.withdrawable = 0;
oracle.aggregator = *aggregator_info.key;
Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
@ -206,12 +214,13 @@ impl Processor {
return Err(Error::NotFoundAggregator.into());
}
let mut oracles = aggregator.oracles;
// remove submission
let mut submissions = aggregator.submissions;
let mut found = false;
for o in oracles.iter_mut() {
if o == &oracle {
*o = Pubkey::default();
for s in submissions.iter_mut() {
if s.oracle == oracle {
*s = Submission::default();
found = true;
break;
}
@ -221,7 +230,7 @@ impl Processor {
return Err(Error::NotFoundOracle.into());
}
aggregator.oracles = oracles;
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
Ok(())
@ -244,7 +253,7 @@ impl Processor {
let clock_sysvar_info = next_account_info(account_info_iter)?;
let oracle_info = next_account_info(account_info_iter)?;
let aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
if !aggregator.is_initialized {
return Err(Error::NotFoundAggregator.into());
}
@ -262,14 +271,38 @@ impl Processor {
return Err(Error::NotFoundOracle.into());
}
if &oracle.aggregator != aggregator_info.key {
return Err(Error::AggregatorKeyNotMatch.into());
}
let clock = &Clock::from_account_info(clock_sysvar_info)?;
// check whether the aggregator owned this oracle
let mut found = false;
let mut submissions = aggregator.submissions;
for s in submissions.iter_mut() {
if &s.oracle == oracle_info.key {
s.value = submission;
s.time = clock.unix_timestamp;
found = true;
break;
}
}
if !found {
return Err(Error::NotFoundOracle.into());
}
aggregator.submissions = submissions;
Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
if oracle.next_submit_time > clock.unix_timestamp {
return Err(Error::SubmissonCooling.into());
}
oracle.submission = submission;
oracle.withdrawable += PAYMENT_AMOUNT;
oracle.next_submit_time = clock.unix_timestamp + SUBMIT_INTERVAL;
oracle.next_submit_time = clock.unix_timestamp + aggregator.submit_interval as i64;
Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
@ -370,6 +403,7 @@ impl PrintProgramError for Error {
Error::SubmissonValueOutOfRange => info!("Error: Submisson value out of range"),
Error::SubmissonCooling => info!("Submission cooling"),
Error::InsufficientWithdrawable => info!("Insufficient withdrawable"),
Error::AggregatorKeyNotMatch => info!("Aggregator key not match"),
}
}
}

View File

@ -7,7 +7,6 @@ use solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
pubkey::Pubkey,
info,
clock::{UnixTimestamp},
};
@ -15,6 +14,8 @@ use solana_program::{
#[repr(C)]
#[derive(Clone, Debug, Copy, Default, PartialEq)]
pub struct Aggregator {
/// The interval(seconds) of an oracle's each submission
pub submit_interval: u32,
/// min submission value
pub min_submission_value: u64,
/// max submission value
@ -23,8 +24,8 @@ pub struct Aggregator {
pub description: [u8; 32],
/// is initialized
pub is_initialized: bool,
/// oracles
pub oracles: [Pubkey; MAX_ORACLES],
/// submissions
pub submissions: [Submission; MAX_ORACLES],
}
impl IsInitialized for Aggregator {
@ -35,16 +36,17 @@ impl IsInitialized for Aggregator {
impl Sealed for Aggregator {}
impl Pack for Aggregator {
const LEN: usize = 49 + MAX_ORACLES*32;
const LEN: usize = 53 + MAX_ORACLES*48;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, 49 + MAX_ORACLES*32];
let src = array_ref![src, 0, 53 + MAX_ORACLES*48];
let (
submit_interval,
min_submission_value,
max_submission_value,
description,
is_initialized,
rem,
) = array_refs![src, 8, 8, 32, 1; ..;];
submissions,
) = array_refs![src, 4, 8, 8, 32, 1, MAX_ORACLES*48];
let is_initialized = match is_initialized {
[0] => false,
@ -53,39 +55,43 @@ impl Pack for Aggregator {
};
Ok(Aggregator {
submit_interval: u32::from_le_bytes(*submit_interval),
min_submission_value: u64::from_le_bytes(*min_submission_value),
max_submission_value: u64::from_le_bytes(*max_submission_value),
description: *description,
is_initialized,
oracles: unpack_oracles(rem),
submissions: unpack_submissions(submissions),
})
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, 49 + MAX_ORACLES*32];
let dst = array_mut_ref![dst, 0, 53 + MAX_ORACLES*48];
let (
submit_interval_dst,
min_submission_value_dst,
max_submission_value_dst,
description_dst,
is_initialized_dst,
rem,
) = mut_array_refs![dst, 8, 8, 32, 1; ..;];
submissions_dst,
) = mut_array_refs![dst, 4, 8, 8, 32, 1, MAX_ORACLES*48];
let &Aggregator {
submit_interval,
min_submission_value,
max_submission_value,
description,
is_initialized,
ref oracles,
ref submissions,
} = self;
*submit_interval_dst = submit_interval.to_le_bytes();
*min_submission_value_dst = min_submission_value.to_le_bytes();
*max_submission_value_dst = max_submission_value.to_le_bytes();
*description_dst = description;
is_initialized_dst[0] = is_initialized as u8;
pack_oracles(oracles, rem);
pack_submissions(submissions, submissions_dst);
}
}
@ -95,7 +101,7 @@ impl Pack for Aggregator {
pub struct Oracle {
/// submission
pub submission: u64,
/// submit times
/// submit time
pub next_submit_time: UnixTimestamp,
/// is usually the oracle name
pub description: [u8; 32],
@ -103,6 +109,8 @@ pub struct Oracle {
pub is_initialized: bool,
/// withdrawable
pub withdrawable: u64,
/// aggregator
pub aggregator: Pubkey,
}
impl IsInitialized for Oracle {
@ -113,13 +121,14 @@ impl IsInitialized for Oracle {
impl Sealed for Oracle {}
impl Pack for Oracle {
const LEN: usize = 57;
const LEN: usize = 89;
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let src = array_ref![src, 0, 57];
let src = array_ref![src, 0, 89];
let (
submission, next_submit_time, description, is_initialized, withdrawable,
) = array_refs![src, 8, 8, 32, 1, 8];
submission, next_submit_time, description, is_initialized,
withdrawable, aggregator,
) = array_refs![src, 8, 8, 32, 1, 8, 32];
let is_initialized = match is_initialized {
[0] => false,
@ -133,19 +142,21 @@ impl Pack for Oracle {
description: *description,
is_initialized,
withdrawable: u64::from_le_bytes(*withdrawable),
aggregator: Pubkey::new_from_array(*aggregator),
})
}
fn pack_into_slice(&self, dst: &mut [u8]) {
let dst = array_mut_ref![dst, 0, 57];
let dst = array_mut_ref![dst, 0, 89];
let (
submission_dst,
next_submit_time_dst,
description_dst,
is_initialized_dst,
withdrawable_dst,
) = mut_array_refs![dst, 8, 8, 32, 1, 8];
aggregator_dst,
) = mut_array_refs![dst, 8, 8, 32, 1, 8, 32];
let &Oracle {
submission,
@ -153,6 +164,7 @@ impl Pack for Oracle {
description,
is_initialized,
withdrawable,
aggregator,
} = self;
*submission_dst = submission.to_le_bytes();
@ -160,26 +172,55 @@ impl Pack for Oracle {
*description_dst = description;
is_initialized_dst[0] = is_initialized as u8;
*withdrawable_dst = withdrawable.to_le_bytes();
aggregator_dst.copy_from_slice(aggregator.as_ref());
}
}
/// Submission data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Submission {
/// submit time
pub time: UnixTimestamp,
/// value
pub value: u64,
/// oracle
pub oracle: Pubkey,
}
// Helpers
fn unpack_oracles(mut dst: &[u8]) -> [Pubkey; MAX_ORACLES] {
let mut arr = [Pubkey::default(); MAX_ORACLES];
fn unpack_submissions(mut dst: &[u8]) -> [Submission; MAX_ORACLES] {
let mut arr = [Submission::default(); MAX_ORACLES];
for i in 0 .. MAX_ORACLES {
let ( pubkey, rem ) = array_refs![dst, 32; ..;];
arr[i] = Pubkey::new_from_array(*pubkey);
let ( submission, rem ) = array_refs![dst, 48; ..;];
let ( time, value, oracle ) = array_refs![submission, 8, 8, 32];
arr[i] = Submission {
time: i64::from_le_bytes(*time),
value: u64::from_le_bytes(*value),
oracle: Pubkey::new_from_array(*oracle),
};
dst = rem;
}
arr
}
fn pack_oracles(src: &[Pubkey; MAX_ORACLES], mut dst: &mut [u8]) {
fn pack_submissions(src: &[Submission; MAX_ORACLES], mut dst: &mut [u8]) {
for i in 0 .. MAX_ORACLES {
let (s, rem) = mut_array_refs![dst, 32; ..;];
let ( submission, rem ) = mut_array_refs![dst, 48; ..;];
s.copy_from_slice(src[i].as_ref());
let (
time_dst,
value_dst,
oracle_dst,
) = mut_array_refs![&mut *submission, 8, 8, 32];
let &Submission { time, value, oracle } = &src[i];
*time_dst = time.to_le_bytes();
*value_dst = value.to_le_bytes();
oracle_dst.copy_from_slice(oracle.as_ref());
dst = rem;
}