prelim implementation of oracle submit
This commit is contained in:
parent
c3c5028ff1
commit
ebdeff24e6
|
@ -17,7 +17,8 @@ fn process_instruction<'a>(
|
|||
) -> ProgramResult {
|
||||
if let Err(error) = Processor::process(program_id, accounts, instruction_data) {
|
||||
// catch the error so we can print it
|
||||
error.print::<Error>();
|
||||
// error.print::<Error>();
|
||||
// msg!("{:?}", error);
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -45,41 +45,51 @@ pub enum Error {
|
|||
InsufficientWithdrawable,
|
||||
/// Aggregator key not match
|
||||
#[error("Aggregator key not match")]
|
||||
AggregatorKeyNotMatch,
|
||||
/// Max oralces reached
|
||||
#[error("Max oracles reached")]
|
||||
MaxOralcesReached,
|
||||
AggregatorMismatch,
|
||||
|
||||
#[error("Invalid round id")]
|
||||
InvalidRoundID,
|
||||
|
||||
#[error("Cannot start new round until cooldown")]
|
||||
OracleNewRoundCooldown,
|
||||
|
||||
#[error("Max number of submissions reached for this round")]
|
||||
MaxSubmissionsReached,
|
||||
|
||||
#[error("Each oracle may only submit once per round")]
|
||||
OracleAlreadySubmitted,
|
||||
}
|
||||
|
||||
impl PrintProgramError for Error {
|
||||
fn print<E>(&self)
|
||||
where
|
||||
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
||||
{
|
||||
match self {
|
||||
Error::InvalidInstruction => msg!("Error: Invalid instruction"),
|
||||
Error::AlreadyInUse => msg!("Error: Already in use"),
|
||||
Error::NotRentExempt => msg!("Error: No rent exempt"),
|
||||
Error::NotFoundAggregator => msg!("Error: no found aggregator"),
|
||||
Error::OracleExist => msg!("Error: Oracle exist"),
|
||||
Error::OwnerMismatch => msg!("Error: Owner mismatch"),
|
||||
Error::NotFoundOracle => msg!("Error: Not found oracle"),
|
||||
Error::SubmissonValueOutOfRange => msg!("Error: Submisson value out of range"),
|
||||
Error::SubmissonCooling => msg!("Submission cooling"),
|
||||
Error::InsufficientWithdrawable => msg!("Insufficient withdrawable"),
|
||||
Error::AggregatorKeyNotMatch => msg!("Aggregator key not match"),
|
||||
Error::MaxOralcesReached => msg!("Max oracles reached"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl PrintProgramError for Error {
|
||||
// fn print<E>(&self)
|
||||
// where
|
||||
// E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
||||
// {
|
||||
// match self {
|
||||
// Error::InvalidInstruction => msg!("Error: Invalid instruction"),
|
||||
// Error::AlreadyInUse => msg!("Error: Already in use"),
|
||||
// Error::NotRentExempt => msg!("Error: No rent exempt"),
|
||||
// Error::NotFoundAggregator => msg!("Error: no found aggregator"),
|
||||
// Error::OracleExist => msg!("Error: Oracle exist"),
|
||||
// Error::OwnerMismatch => msg!("Error: Owner mismatch"),
|
||||
// Error::NotFoundOracle => msg!("Error: Not found oracle"),
|
||||
// Error::SubmissonValueOutOfRange => msg!("Error: Submisson value out of range"),
|
||||
// Error::SubmissonCooling => msg!("Submission cooling"),
|
||||
// Error::InsufficientWithdrawable => msg!("Insufficient withdrawable"),
|
||||
// Error::AggregatorMismatch => msg!("Aggregator key not match"),
|
||||
// Error::MaxOralcesReached => msg!("Max oracles reached"),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<Error> for ProgramError {
|
||||
fn from(e: Error) -> Self {
|
||||
ProgramError::Custom(e as u32)
|
||||
}
|
||||
}
|
||||
impl<T> DecodeError<T> for Error {
|
||||
fn type_of() -> &'static str {
|
||||
"Error"
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T> DecodeError<T> for Error {
|
||||
// fn type_of() -> &'static str {
|
||||
// "Error"
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -27,16 +27,9 @@ pub enum Instruction {
|
|||
/// Remove an oracle
|
||||
RemoveOracle,
|
||||
|
||||
/// Called by oracles when they have witnessed a need to update
|
||||
///
|
||||
/// Accounts expected by this instruction:
|
||||
/// 0. `[writable]` The aggregator(key).
|
||||
/// 1. `[]` Clock sysvar
|
||||
/// 2. `[writable]` The oracle key.
|
||||
/// 3. `[signer]` The oracle owner.
|
||||
Submit {
|
||||
/// the updated data that the oracle is submitting
|
||||
submission: u64,
|
||||
round_id: u64,
|
||||
value: u64,
|
||||
},
|
||||
|
||||
/// Oracle withdraw token
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Program state processor
|
||||
|
||||
use crate::{error::Error, instruction::{Instruction, PAYMENT_AMOUNT}, state::{Aggregator, AggregatorConfig, Oracle}};
|
||||
use std::default;
|
||||
|
||||
use crate::{error::Error, instruction::{Instruction, PAYMENT_AMOUNT}, state::{Aggregator, AggregatorConfig, Oracle, Round}};
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
|
@ -9,7 +11,7 @@ use solana_program::{
|
|||
msg,
|
||||
program::invoke_signed,
|
||||
program_error::ProgramError,
|
||||
program_pack::Pack,
|
||||
program_pack::{IsInitialized},
|
||||
pubkey::Pubkey,
|
||||
sysvar::{rent::Rent, Sysvar},
|
||||
};
|
||||
|
@ -30,6 +32,10 @@ impl <'a, 'b>Accounts<'a, 'b> {
|
|||
fn get_rent(&self, i: usize) -> Result<Rent, ProgramError> {
|
||||
Rent::from_account_info(self.get(i)?)
|
||||
}
|
||||
|
||||
fn get_clock(&self, i: usize) -> Result<Clock, ProgramError> {
|
||||
Clock::from_account_info(self.get(i)?)
|
||||
}
|
||||
}
|
||||
|
||||
struct InitializeContext<'a> {
|
||||
|
@ -108,7 +114,7 @@ impl <'a>RemoveOracleContext<'a> {
|
|||
|
||||
let oracle = Oracle::load_initialized(self.oracle)?;
|
||||
if oracle.aggregator != self.aggregator.key.to_bytes() {
|
||||
return Err(Error::OwnerMismatch)?;
|
||||
return Err(Error::AggregatorMismatch)?;
|
||||
}
|
||||
|
||||
// Zero out the oracle account memory. This allows reuse or reclaim.
|
||||
|
@ -119,6 +125,122 @@ impl <'a>RemoveOracleContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
struct SubmitContext<'a> {
|
||||
clock: Clock,
|
||||
aggregator: &'a AccountInfo<'a>,
|
||||
oracle: &'a AccountInfo<'a>,
|
||||
oracle_owner: &'a AccountInfo<'a>, // signed
|
||||
|
||||
// NOTE: 5.84942*10^11 years even if 1 sec per round. don't bother with handling wrapparound.
|
||||
round_id: u64,
|
||||
value: u64,
|
||||
}
|
||||
|
||||
impl <'a>SubmitContext<'a> {
|
||||
fn process(&self) -> ProgramResult {
|
||||
let mut aggregator = Aggregator::load_initialized(self.aggregator)?;
|
||||
let mut oracle = Oracle::load_initialized(self.oracle)?;
|
||||
|
||||
if !self.oracle_owner.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
if oracle.aggregator != self.aggregator.key.to_bytes() {
|
||||
return Err(Error::AggregatorMismatch)?;
|
||||
}
|
||||
|
||||
// oracle starts a new round
|
||||
if self.round_id == aggregator.current_round.id + 1 {
|
||||
self.start_new_round(&mut aggregator, &mut oracle)?;
|
||||
}
|
||||
|
||||
// only allowed to submit in the current round (or a new round that just
|
||||
// got started)
|
||||
if self.round_id != aggregator.current_round.id {
|
||||
return Err(Error::InvalidRoundID)?;
|
||||
}
|
||||
|
||||
self.submit(&mut aggregator, &oracle)?;
|
||||
|
||||
// credit oracle for submission
|
||||
oracle.withdrawable = aggregator.config.reward_amount;
|
||||
|
||||
aggregator.save(self.aggregator)?;
|
||||
oracle.save(self.oracle)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// push oracle answer to the current round. update answer if min submissions
|
||||
/// had been satisfied.
|
||||
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 count = i + 1;
|
||||
|
||||
if count > aggregator.config.max_submissions as usize {
|
||||
return Err(Error::MaxSubmissionsReached)?;
|
||||
}
|
||||
|
||||
if submission.is_initialized() {
|
||||
return Err(Error::OracleAlreadySubmitted)?;
|
||||
}
|
||||
|
||||
submission.updated_at = now;
|
||||
submission.value = self.value;
|
||||
submission.oracle = self.oracle.key.to_bytes();
|
||||
|
||||
if count < aggregator.config.min_submissions as usize {
|
||||
// not enough submissions to update answer. return now.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let new_submission = *submission;
|
||||
// update answer if the new round reached min_submissions
|
||||
let round = &aggregator.current_round;
|
||||
let answer = &mut aggregator.answer;
|
||||
|
||||
if round.id != answer.round_id {
|
||||
// a new round had just been resolved. copy the current round's submissions over
|
||||
answer.round_id = round.id;
|
||||
answer.created_at = now;
|
||||
answer.updated_at = now;
|
||||
answer.submissions = round.submissions;
|
||||
} else {
|
||||
answer.updated_at = now;
|
||||
answer.submissions[i] = new_submission;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(Error::OracleNewRoundCooldown)?;
|
||||
}
|
||||
|
||||
// zero the submissions of the current round
|
||||
aggregator.current_round = Round {
|
||||
id: self.round_id,
|
||||
started_at: now,
|
||||
..Round::default()
|
||||
};
|
||||
|
||||
// oracle can start new round after `1 + restart_delay` rounds
|
||||
oracle.allow_start_round = self.round_id + aggregator.config.restart_delay + 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Program state handler.
|
||||
pub struct Processor {}
|
||||
|
||||
|
@ -156,8 +278,19 @@ impl Processor {
|
|||
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()
|
||||
},
|
||||
_ => {
|
||||
Ok(())
|
||||
Err(ProgramError::InvalidInstructionData)
|
||||
}
|
||||
// Instruction::Submit { submission } => {
|
||||
// msg!("Instruction: Submit");
|
||||
|
@ -170,75 +303,6 @@ impl Processor {
|
|||
}
|
||||
}
|
||||
|
||||
// /// Processes an [Submit](enum.Instruction.html) instruction.
|
||||
// pub fn process_submit(accounts: &[AccountInfo], submission: u64) -> ProgramResult {
|
||||
// let account_info_iter = &mut accounts.iter();
|
||||
// let aggregator_info = next_account_info(account_info_iter)?;
|
||||
// let clock_sysvar_info = next_account_info(account_info_iter)?;
|
||||
// let oracle_info = next_account_info(account_info_iter)?;
|
||||
// let oracle_owner_info = next_account_info(account_info_iter)?;
|
||||
|
||||
// let mut aggregator = Aggregator::unpack_unchecked(&aggregator_info.data.borrow())?;
|
||||
// if !aggregator.is_initialized {
|
||||
// return Err(Error::NotFoundAggregator.into());
|
||||
// }
|
||||
|
||||
// if submission < aggregator.min_submission_value
|
||||
// || submission > aggregator.max_submission_value
|
||||
// {
|
||||
// return Err(Error::SubmissonValueOutOfRange.into());
|
||||
// }
|
||||
|
||||
// if !oracle_owner_info.is_signer {
|
||||
// return Err(ProgramError::MissingRequiredSignature);
|
||||
// }
|
||||
|
||||
// let mut oracle = Oracle::unpack_unchecked(&oracle_info.data.borrow())?;
|
||||
// if !oracle.is_initialized {
|
||||
// return Err(Error::NotFoundOracle.into());
|
||||
// }
|
||||
|
||||
// if &Pubkey::new_from_array(oracle.owner) != oracle_owner_info.key {
|
||||
// return Err(Error::OwnerMismatch.into());
|
||||
// }
|
||||
|
||||
// if &Pubkey::new_from_array(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;
|
||||
// for s in aggregator.submissions.iter_mut() {
|
||||
// if &Pubkey::new_from_array(s.oracle) == oracle_info.key {
|
||||
// s.value = submission;
|
||||
// s.time = clock.unix_timestamp;
|
||||
// found = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !found {
|
||||
// return Err(Error::NotFoundOracle.into());
|
||||
// }
|
||||
|
||||
// if oracle.next_submit_time > clock.unix_timestamp {
|
||||
// return Err(Error::SubmissonCooling.into());
|
||||
// }
|
||||
|
||||
// oracle.withdrawable += PAYMENT_AMOUNT;
|
||||
// oracle.next_submit_time = clock.unix_timestamp + aggregator.submit_interval as i64;
|
||||
|
||||
// // update aggregator
|
||||
// Aggregator::pack(aggregator, &mut aggregator_info.data.borrow_mut())?;
|
||||
|
||||
// // update oracle
|
||||
// Oracle::pack(oracle, &mut oracle_info.data.borrow_mut())?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// /// Processes an [Withdraw](enum.Instruction.html) instruction
|
||||
// pub fn process_withdraw(
|
||||
// accounts: &[AccountInfo],
|
||||
|
@ -407,11 +471,9 @@ mod tests {
|
|||
&program_id,
|
||||
instruction::Instruction::Initialize {
|
||||
config: AggregatorConfig{
|
||||
submit_interval: 10,
|
||||
min_submission_value: 0,
|
||||
max_submission_value: 100,
|
||||
submission_decimals: 8,
|
||||
decimals: 8,
|
||||
description: [0u8; 32],
|
||||
..AggregatorConfig::default()
|
||||
}
|
||||
},
|
||||
vec![
|
||||
|
|
|
@ -4,23 +4,27 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
|||
use crate::instruction::MAX_ORACLES;
|
||||
use crate::borsh_state::{InitBorshState, BorshState};
|
||||
|
||||
use solana_program::{
|
||||
clock::UnixTimestamp,
|
||||
program_pack::IsInitialized,
|
||||
};
|
||||
use solana_program::{program_pack::IsInitialized};
|
||||
|
||||
#[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
|
||||
pub min_submission_value: u64,
|
||||
/// max submission value
|
||||
pub max_submission_value: u64,
|
||||
/// submission decimals
|
||||
pub submission_decimals: u8,
|
||||
/// decimals for this feed
|
||||
pub decimals: u8,
|
||||
|
||||
/// description
|
||||
pub description: [u8; 32],
|
||||
|
||||
/// oracle cannot start a new round until after `restart_relay` rounds
|
||||
pub restart_delay: u64,
|
||||
|
||||
/// amount of tokens oracles are reward per submission
|
||||
pub reward_amount: u64,
|
||||
|
||||
/// max number of submissions in a round
|
||||
pub max_submissions: u8,
|
||||
|
||||
/// min number of submissions in a round to resolve an answer
|
||||
pub min_submissions: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
|
||||
|
@ -65,13 +69,18 @@ impl InitBorshState for Aggregator {}
|
|||
#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
|
||||
pub struct Submission {
|
||||
/// submit time
|
||||
pub time: UnixTimestamp,
|
||||
pub updated_at: u64,
|
||||
/// value
|
||||
pub value: u64,
|
||||
/// oracle
|
||||
pub oracle: [u8; 32],
|
||||
}
|
||||
|
||||
impl IsInitialized for Submission {
|
||||
fn is_initialized(&self) -> bool {
|
||||
self.updated_at > 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Oracle data.
|
||||
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, Default, PartialEq)]
|
||||
|
@ -82,6 +91,10 @@ pub struct Oracle {
|
|||
pub is_initialized: bool,
|
||||
/// withdrawable
|
||||
pub withdrawable: u64,
|
||||
|
||||
/// oracle cannot start a new round until after `restart_relay` rounds
|
||||
pub allow_start_round: u64,
|
||||
|
||||
/// aggregator
|
||||
pub aggregator: [u8; 32],
|
||||
/// owner
|
||||
|
|
Loading…
Reference in New Issue