From afc7e766cf9eae14e86db5ee08dbf20773cf2556 Mon Sep 17 00:00:00 2001 From: czl1378 Date: Tue, 8 Dec 2020 11:57:03 +0800 Subject: [PATCH] store submissions in the aggregator account --- Cargo.toml | 1 + src/error.rs | 3 ++ src/instruction.rs | 21 +++++++--- src/lib.rs | 45 ++++++++++++++++++++- src/processor.rs | 74 +++++++++++++++++++++++++---------- src/state.rs | 97 +++++++++++++++++++++++++++++++++------------- 6 files changed, 186 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8aa295..9d74901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ crate-type = ["cdylib", "lib"] [features] program = [] +no-entrypoint = [] [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 2b1b732..86bfe97 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index f3296f0..44fcf4b 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -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); diff --git a/src/lib.rs b/src/lib.rs index 72a149f..53103e5 100644 --- a/src/lib.rs +++ b/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 { + 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; \ No newline at end of file diff --git a/src/processor.rs b/src/processor.rs index 5eb3b25..32fca8b 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -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"), } } } \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 2339871..0fb7105 100644 --- a/src/state.rs +++ b/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 { - 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 { - 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; }