store submissions in the aggregator account
This commit is contained in:
parent
5525a5d506
commit
afc7e766cf
|
@ -21,6 +21,7 @@ crate-type = ["cdylib", "lib"]
|
|||
|
||||
[features]
|
||||
program = []
|
||||
no-entrypoint = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
45
src/lib.rs
45
src/lib.rs
|
@ -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;
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
97
src/state.rs
97
src/state.rs
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue