2020-11-26 14:19:50 -08:00
|
|
|
//! Program state processor
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
use crate::{
|
|
|
|
error::Error,
|
2021-02-18 07:38:14 -08:00
|
|
|
instruction::{self, Instruction},
|
2021-02-05 00:33:48 -08:00
|
|
|
state::{Aggregator, AggregatorConfig, Authority, Oracle, Round, Submissions},
|
2021-02-03 17:50:04 -08:00
|
|
|
};
|
2020-11-27 22:52:54 -08:00
|
|
|
|
2021-02-18 00:37:23 -08:00
|
|
|
// use spl_token::state;
|
2021-02-18 07:38:14 -08:00
|
|
|
use solana_program::{
|
|
|
|
account_info::AccountInfo,
|
|
|
|
clock::Clock,
|
|
|
|
entrypoint::ProgramResult,
|
|
|
|
msg,
|
|
|
|
program::invoke_signed,
|
|
|
|
program_error::ProgramError,
|
|
|
|
program_pack::{IsInitialized, Pack},
|
|
|
|
pubkey::Pubkey,
|
|
|
|
sysvar::{rent::Rent, Sysvar},
|
|
|
|
};
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
use crate::borsh_state::{BorshState, InitBorshState};
|
2021-02-03 01:59:21 -08:00
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
use borsh::BorshDeserialize;
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-18 07:38:14 -08:00
|
|
|
struct Accounts<'a>(&'a [AccountInfo<'a>]);
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-18 07:38:14 -08:00
|
|
|
impl<'a> Accounts<'a> {
|
|
|
|
fn get(&self, i: usize) -> Result<&'a AccountInfo<'a>, ProgramError> {
|
2021-02-03 17:50:04 -08:00
|
|
|
// fn get(&self, i: usize) -> Result<&AccountInfo, ProgramError> {
|
|
|
|
// &accounts[input.token.account as usize]
|
2021-02-02 18:42:53 -08:00
|
|
|
self.0.get(i).ok_or(ProgramError::NotEnoughAccountKeys)
|
2020-11-26 14:19:50 -08:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
fn get_rent(&self, i: usize) -> Result<Rent, ProgramError> {
|
|
|
|
Rent::from_account_info(self.get(i)?)
|
2020-11-26 14:19:50 -08:00
|
|
|
}
|
2021-02-03 03:33:45 -08:00
|
|
|
|
|
|
|
fn get_clock(&self, i: usize) -> Result<Clock, ProgramError> {
|
|
|
|
Clock::from_account_info(self.get(i)?)
|
|
|
|
}
|
2021-02-02 18:42:53 -08:00
|
|
|
}
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
struct InitializeContext<'a> {
|
|
|
|
rent: Rent,
|
|
|
|
aggregator: &'a AccountInfo<'a>,
|
2021-02-06 00:12:40 -08:00
|
|
|
aggregator_owner: &'a AccountInfo<'a>, // signed
|
|
|
|
round_submissions: &'a AccountInfo<'a>, // belongs_to: aggregator
|
2021-02-05 00:33:48 -08:00
|
|
|
answer_submissions: &'a AccountInfo<'a>, // belongs_to: aggregator
|
2020-11-27 01:38:49 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
config: AggregatorConfig,
|
2021-02-02 18:42:53 -08:00
|
|
|
}
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
impl<'a> InitializeContext<'a> {
|
2021-02-02 18:42:53 -08:00
|
|
|
fn process(&self) -> ProgramResult {
|
2021-02-04 01:56:09 -08:00
|
|
|
if !self.aggregator_owner.is_signer {
|
2020-12-01 19:42:09 -08:00
|
|
|
return Err(ProgramError::MissingRequiredSignature);
|
|
|
|
}
|
2021-01-08 09:39:16 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
self.init_submissions(self.round_submissions)?;
|
|
|
|
self.init_submissions(self.answer_submissions)?;
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut aggregator = Aggregator::init_uninitialized(self.aggregator)?;
|
|
|
|
aggregator.is_initialized = true;
|
|
|
|
aggregator.config = self.config.clone();
|
2021-02-05 00:33:48 -08:00
|
|
|
aggregator.owner = self.aggregator_owner.into();
|
|
|
|
// aggregator.round_submissions = PublicKey(self.round_submissions.key.to_bytes());
|
|
|
|
aggregator.round_submissions = self.round_submissions.into();
|
|
|
|
aggregator.answer_submissions = self.answer_submissions.into();
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
aggregator.save_exempt(self.aggregator, &self.rent)?;
|
2020-11-27 22:52:54 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-05 00:33:48 -08:00
|
|
|
|
|
|
|
fn init_submissions(&self, account: &AccountInfo) -> ProgramResult {
|
|
|
|
let mut submissions = Submissions::init_uninitialized(account)?;
|
|
|
|
submissions.is_initialized = true;
|
|
|
|
submissions.save_exempt(account, &self.rent)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-03 01:59:21 -08:00
|
|
|
}
|
|
|
|
|
2021-02-04 01:56:09 -08:00
|
|
|
struct ConfigureContext<'a> {
|
|
|
|
aggregator: &'a AccountInfo<'a>,
|
|
|
|
aggregator_owner: &'a AccountInfo<'a>,
|
|
|
|
|
|
|
|
config: AggregatorConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> ConfigureContext<'a> {
|
|
|
|
fn process(&self) -> ProgramResult {
|
|
|
|
let mut aggregator = Aggregator::load_initialized(&self.aggregator)?;
|
|
|
|
aggregator.authorize(self.aggregator_owner)?;
|
|
|
|
aggregator.config = self.config.clone();
|
|
|
|
aggregator.save(self.aggregator)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
struct AddOracleContext<'a> {
|
|
|
|
rent: Rent,
|
|
|
|
aggregator: &'a AccountInfo<'a>,
|
|
|
|
aggregator_owner: &'a AccountInfo<'a>, // signed
|
|
|
|
oracle: &'a AccountInfo<'a>,
|
|
|
|
oracle_owner: &'a AccountInfo<'a>,
|
|
|
|
|
|
|
|
description: [u8; 32],
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
impl<'a> AddOracleContext<'a> {
|
2021-02-03 01:59:21 -08:00
|
|
|
fn process(&self) -> ProgramResult {
|
|
|
|
// Note: there can in fact be more oracles than max_submissions
|
|
|
|
let aggregator = Aggregator::load_initialized(self.aggregator)?;
|
2021-02-15 00:17:45 -08:00
|
|
|
msg!("loaded aggregator");
|
2021-02-04 01:35:34 -08:00
|
|
|
aggregator.authorize(self.aggregator_owner)?;
|
2020-12-04 04:11:10 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut oracle = Oracle::init_uninitialized(self.oracle)?;
|
2021-02-15 00:17:45 -08:00
|
|
|
msg!("loaded oracle");
|
2021-02-03 01:59:21 -08:00
|
|
|
oracle.is_initialized = true;
|
|
|
|
oracle.description = self.description;
|
2021-02-05 00:33:48 -08:00
|
|
|
oracle.owner = self.oracle_owner.into();
|
|
|
|
oracle.aggregator = self.aggregator.into();
|
2021-02-03 01:59:21 -08:00
|
|
|
oracle.save_exempt(self.oracle, &self.rent)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct RemoveOracleContext<'a> {
|
|
|
|
aggregator: &'a AccountInfo<'a>,
|
|
|
|
aggregator_owner: &'a AccountInfo<'a>, // signed
|
|
|
|
oracle: &'a AccountInfo<'a>,
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
impl<'a> RemoveOracleContext<'a> {
|
2021-02-03 01:59:21 -08:00
|
|
|
fn process(&self) -> ProgramResult {
|
|
|
|
let aggregator = Aggregator::load_initialized(self.aggregator)?;
|
2021-02-04 01:35:34 -08:00
|
|
|
aggregator.authorize(self.aggregator_owner)?;
|
2021-02-03 01:59:21 -08:00
|
|
|
|
|
|
|
let oracle = Oracle::load_initialized(self.oracle)?;
|
2021-02-05 00:33:48 -08:00
|
|
|
if oracle.aggregator.0 != self.aggregator.key.to_bytes() {
|
2021-02-03 03:33:45 -08:00
|
|
|
return Err(Error::AggregatorMismatch)?;
|
2021-02-03 01:59:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Zero out the oracle account memory. This allows reuse or reclaim.
|
|
|
|
// Note: will wipe out withdrawable balance on this oracle. Too bad.
|
|
|
|
Oracle::default().save(self.oracle)?;
|
2020-11-27 22:52:54 -08:00
|
|
|
|
2020-11-26 14:19:50 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-02 18:42:53 -08:00
|
|
|
}
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2021-02-03 03:33:45 -08:00
|
|
|
struct SubmitContext<'a> {
|
|
|
|
clock: Clock,
|
|
|
|
aggregator: &'a AccountInfo<'a>,
|
2021-02-05 00:33:48 -08:00
|
|
|
round_submissions: &'a AccountInfo<'a>,
|
|
|
|
answer_submissions: &'a AccountInfo<'a>,
|
2021-02-03 03:33:45 -08:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
impl<'a> SubmitContext<'a> {
|
2021-02-03 03:33:45 -08:00
|
|
|
fn process(&self) -> ProgramResult {
|
|
|
|
let mut aggregator = Aggregator::load_initialized(self.aggregator)?;
|
|
|
|
let mut oracle = Oracle::load_initialized(self.oracle)?;
|
2021-02-04 01:35:34 -08:00
|
|
|
oracle.authorize(self.oracle_owner)?;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
if oracle.aggregator.0 != self.aggregator.key.to_bytes() {
|
2021-02-03 03:33:45 -08:00
|
|
|
return Err(Error::AggregatorMismatch)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// oracle starts a new round
|
2021-02-05 00:33:48 -08:00
|
|
|
if self.round_id == aggregator.round.id + 1 {
|
2021-02-03 03:33:45 -08:00
|
|
|
self.start_new_round(&mut aggregator, &mut oracle)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only allowed to submit in the current round (or a new round that just
|
|
|
|
// got started)
|
2021-02-05 00:33:48 -08:00
|
|
|
if self.round_id != aggregator.round.id {
|
2021-02-03 03:33:45 -08:00
|
|
|
return Err(Error::InvalidRoundID)?;
|
|
|
|
}
|
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
self.submit(&mut aggregator)?;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
|
|
|
// credit oracle for submission
|
2021-02-04 01:43:00 -08:00
|
|
|
oracle.withdrawable = oracle
|
|
|
|
.withdrawable
|
|
|
|
.checked_add(aggregator.config.reward_amount)
|
|
|
|
.ok_or(Error::RewardsOverflow)?;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
|
|
|
aggregator.save(self.aggregator)?;
|
|
|
|
oracle.save(self.oracle)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// push oracle answer to the current round. update answer if min submissions
|
|
|
|
/// had been satisfied.
|
2021-02-03 22:50:12 -08:00
|
|
|
fn submit(&self, aggregator: &mut Aggregator) -> ProgramResult {
|
2021-02-17 05:38:29 -08:00
|
|
|
let now = self.clock.slot;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let mut round_submissions = aggregator.round_submissions(self.round_submissions)?;
|
|
|
|
|
|
|
|
let (i, submission) = round_submissions
|
|
|
|
.data
|
2021-02-03 17:50:04 -08:00
|
|
|
.iter_mut()
|
|
|
|
.enumerate()
|
2021-02-04 00:22:45 -08:00
|
|
|
.find(|(_i, s)| {
|
2021-02-03 17:50:04 -08:00
|
|
|
// either finds a new spot to put the submission, or find a spot
|
|
|
|
// that the oracle previously submitted to.
|
2021-02-03 22:50:12 -08:00
|
|
|
return !s.is_initialized() || s.oracle == self.oracle.key.to_bytes();
|
2021-02-03 17:50:04 -08:00
|
|
|
})
|
|
|
|
.ok_or(Error::MaxSubmissionsReached)?;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
|
|
|
let count = i + 1;
|
|
|
|
|
|
|
|
if count > aggregator.config.max_submissions as usize {
|
|
|
|
return Err(Error::MaxSubmissionsReached)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if submission.is_initialized() {
|
|
|
|
return Err(Error::OracleAlreadySubmitted)?;
|
|
|
|
}
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
if aggregator.round.created_at == 0 {
|
|
|
|
aggregator.round.created_at = now;
|
2021-02-03 22:50:12 -08:00
|
|
|
}
|
2021-02-05 00:33:48 -08:00
|
|
|
aggregator.round.updated_at = now;
|
2021-02-03 22:50:12 -08:00
|
|
|
|
2021-02-03 03:33:45 -08:00
|
|
|
submission.updated_at = now;
|
|
|
|
submission.value = self.value;
|
|
|
|
submission.oracle = self.oracle.key.to_bytes();
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
// this line is for later, but put here to deal with borrow check...
|
|
|
|
let new_submission = *submission;
|
|
|
|
|
|
|
|
round_submissions.save(self.round_submissions)?;
|
|
|
|
|
2021-02-03 03:33:45 -08:00
|
|
|
if count < aggregator.config.min_submissions as usize {
|
|
|
|
// not enough submissions to update answer. return now.
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// update answer if the new round reached min_submissions
|
2021-02-05 00:33:48 -08:00
|
|
|
let mut answer_submissions = aggregator.answer_submissions(self.answer_submissions)?;
|
|
|
|
let round = &aggregator.round;
|
2021-02-03 03:33:45 -08:00
|
|
|
let answer = &mut aggregator.answer;
|
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
if !answer.is_initialized() || round.id > answer.round_id {
|
2021-02-03 03:33:45 -08:00
|
|
|
// 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;
|
2021-02-05 00:33:48 -08:00
|
|
|
answer_submissions.data = round_submissions.data;
|
2021-02-03 03:33:45 -08:00
|
|
|
} else {
|
|
|
|
answer.updated_at = now;
|
2021-02-05 00:33:48 -08:00
|
|
|
answer_submissions.data[i] = new_submission;
|
2021-02-03 03:33:45 -08:00
|
|
|
}
|
|
|
|
|
2021-02-17 19:12:50 -08:00
|
|
|
answer.median = answer_submissions.median()?;
|
2021-02-05 00:33:48 -08:00
|
|
|
answer_submissions.save(self.answer_submissions)?;
|
|
|
|
|
2021-02-03 03:33:45 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
fn start_new_round(&self, aggregator: &mut Aggregator, oracle: &mut Oracle) -> ProgramResult {
|
2021-02-17 05:38:29 -08:00
|
|
|
let now = self.clock.slot;
|
2021-02-03 03:33:45 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
if aggregator.round.id < oracle.allow_start_round {
|
2021-02-03 03:33:45 -08:00
|
|
|
return Err(Error::OracleNewRoundCooldown)?;
|
|
|
|
}
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
aggregator.round = Round {
|
2021-02-03 03:33:45 -08:00
|
|
|
id: self.round_id,
|
2021-02-04 19:00:19 -08:00
|
|
|
created_at: now,
|
2021-02-05 00:33:48 -08:00
|
|
|
updated_at: 0,
|
2021-02-03 03:33:45 -08:00
|
|
|
};
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
// zero the submissions of the current round
|
|
|
|
let submissions = Submissions {
|
|
|
|
is_initialized: true,
|
|
|
|
data: Default::default(),
|
|
|
|
};
|
|
|
|
submissions.save(self.round_submissions)?;
|
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
// oracle can start new round after `restart_delay` rounds
|
2021-02-04 19:00:19 -08:00
|
|
|
oracle.allow_start_round = self.round_id + (aggregator.config.restart_delay as u64);
|
2021-02-03 03:33:45 -08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-04 00:43:14 -08:00
|
|
|
// Withdraw token from reward faucet to receiver account, deducting oracle's withdrawable credit.
|
2021-02-04 01:35:34 -08:00
|
|
|
struct WithdrawContext<'a, 'b> {
|
|
|
|
token_program: &'a AccountInfo<'a>,
|
2021-02-18 00:37:23 -08:00
|
|
|
aggregator: &'a AccountInfo<'a>,
|
2021-02-04 00:43:14 -08:00
|
|
|
faucet: &'a AccountInfo<'a>,
|
|
|
|
faucet_owner: &'a AccountInfo<'a>, // program signed
|
|
|
|
oracle: &'a AccountInfo<'a>,
|
|
|
|
oracle_owner: &'a AccountInfo<'a>, // signed
|
|
|
|
receiver: &'a AccountInfo<'a>,
|
2021-02-04 01:35:34 -08:00
|
|
|
|
|
|
|
faucet_owner_seed: &'b [u8],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> WithdrawContext<'a, 'b> {
|
|
|
|
fn process(&self) -> ProgramResult {
|
2021-02-18 00:37:23 -08:00
|
|
|
let aggregator = Aggregator::load_initialized(self.aggregator)?;
|
2021-02-04 01:35:34 -08:00
|
|
|
let mut oracle = Oracle::load_initialized(self.oracle)?;
|
|
|
|
oracle.authorize(&self.oracle_owner)?;
|
2021-02-18 00:37:23 -08:00
|
|
|
oracle.check_aggregator(self.aggregator)?;
|
|
|
|
|
2021-02-18 07:38:14 -08:00
|
|
|
if !aggregator
|
|
|
|
.config
|
|
|
|
.reward_token_account
|
|
|
|
.is_account(self.faucet)
|
|
|
|
{
|
|
|
|
return Err(Error::InvalidFaucet)?;
|
2021-02-18 00:37:23 -08:00
|
|
|
}
|
2021-02-04 01:35:34 -08:00
|
|
|
|
|
|
|
if oracle.withdrawable == 0 {
|
|
|
|
return Err(Error::InsufficientWithdrawable)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let amount = oracle.withdrawable;
|
|
|
|
|
|
|
|
oracle.withdrawable = 0;
|
|
|
|
oracle.save(self.oracle)?;
|
|
|
|
|
2021-02-18 00:37:23 -08:00
|
|
|
// The SPL Token ensures that faucet and receiver are the same type of token
|
2021-02-04 01:35:34 -08:00
|
|
|
let inx = spl_token::instruction::transfer(
|
|
|
|
self.token_program.key,
|
|
|
|
self.faucet.key,
|
|
|
|
self.receiver.key,
|
|
|
|
self.faucet_owner.key,
|
|
|
|
&[],
|
|
|
|
amount,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
invoke_signed(
|
|
|
|
&inx,
|
|
|
|
&[
|
|
|
|
self.token_program.clone(),
|
|
|
|
self.faucet.clone(),
|
|
|
|
self.faucet_owner.clone(),
|
|
|
|
self.receiver.clone(),
|
|
|
|
],
|
|
|
|
&[&[self.faucet_owner_seed]],
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-04 00:43:14 -08:00
|
|
|
}
|
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
/// Program state handler.
|
|
|
|
pub struct Processor {}
|
2020-12-08 05:14:05 -08:00
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
impl Processor {
|
2021-02-03 17:50:04 -08:00
|
|
|
pub fn process<'a>(
|
2021-02-04 00:22:45 -08:00
|
|
|
_program_id: &Pubkey,
|
2021-02-03 17:50:04 -08:00
|
|
|
accounts: &'a [AccountInfo<'a>],
|
|
|
|
input: &[u8],
|
|
|
|
) -> ProgramResult {
|
2021-02-02 18:42:53 -08:00
|
|
|
let accounts = Accounts(accounts);
|
2021-02-03 17:50:04 -08:00
|
|
|
let instruction =
|
|
|
|
Instruction::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)?;
|
2020-12-07 19:57:03 -08:00
|
|
|
|
2021-02-18 07:38:14 -08:00
|
|
|
// match branches would increase stack frame, and hit the hard 4096
|
|
|
|
// frame limit. break the other branches into another function call, and
|
|
|
|
// mark it as never inline.
|
2021-02-02 18:42:53 -08:00
|
|
|
match instruction {
|
2021-02-03 17:50:04 -08:00
|
|
|
Instruction::Submit { round_id, value } => SubmitContext {
|
|
|
|
clock: accounts.get_clock(0)?,
|
|
|
|
aggregator: accounts.get(1)?,
|
2021-02-05 00:33:48 -08:00
|
|
|
round_submissions: accounts.get(2)?,
|
|
|
|
answer_submissions: accounts.get(3)?,
|
|
|
|
oracle: accounts.get(4)?,
|
|
|
|
oracle_owner: accounts.get(5)?,
|
2021-02-03 17:50:04 -08:00
|
|
|
round_id,
|
|
|
|
value,
|
|
|
|
}
|
|
|
|
.process(),
|
2021-02-18 07:38:14 -08:00
|
|
|
|
2021-02-04 01:35:34 -08:00
|
|
|
Instruction::Withdraw { faucet_owner_seed } => WithdrawContext {
|
|
|
|
token_program: accounts.get(0)?,
|
2021-02-18 00:37:23 -08:00
|
|
|
aggregator: accounts.get(1)?,
|
2021-02-18 07:38:14 -08:00
|
|
|
faucet: accounts.get(2)?, // write
|
|
|
|
faucet_owner: accounts.get(3)?, // program signed
|
|
|
|
oracle: accounts.get(4)?, // write
|
|
|
|
oracle_owner: accounts.get(5)?, // signed
|
|
|
|
receiver: accounts.get(6)?, // write
|
2021-02-04 01:35:34 -08:00
|
|
|
|
|
|
|
faucet_owner_seed: &faucet_owner_seed[..],
|
|
|
|
}
|
|
|
|
.process(),
|
2021-02-18 07:38:14 -08:00
|
|
|
instruction => process2(instruction, accounts),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
fn process2(instruction: Instruction, accounts: Accounts) -> ProgramResult {
|
|
|
|
match instruction {
|
|
|
|
Instruction::Initialize { config } => InitializeContext {
|
|
|
|
rent: accounts.get_rent(0)?,
|
|
|
|
aggregator: accounts.get(1)?,
|
|
|
|
aggregator_owner: accounts.get(2)?,
|
|
|
|
round_submissions: accounts.get(3)?,
|
|
|
|
answer_submissions: accounts.get(4)?,
|
|
|
|
config,
|
|
|
|
}
|
|
|
|
.process(),
|
|
|
|
Instruction::Configure { config } => ConfigureContext {
|
|
|
|
aggregator: accounts.get(0)?,
|
|
|
|
aggregator_owner: accounts.get(1)?,
|
|
|
|
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)?,
|
2020-12-01 02:50:31 -08:00
|
|
|
}
|
2021-02-18 07:38:14 -08:00
|
|
|
.process(),
|
|
|
|
_ => Err(ProgramError::InvalidInstructionData),
|
2020-11-27 22:52:54 -08:00
|
|
|
}
|
2020-11-30 09:41:09 -08:00
|
|
|
}
|
2020-11-26 14:19:50 -08:00
|
|
|
|
2020-12-17 22:38:28 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2021-02-02 18:42:53 -08:00
|
|
|
|
|
|
|
use crate::instruction;
|
2021-02-05 00:33:48 -08:00
|
|
|
use crate::{borsh_utils, state::Submission};
|
2021-02-03 17:50:04 -08:00
|
|
|
use borsh::BorshSerialize;
|
|
|
|
use solana_program::sysvar;
|
2021-02-02 18:42:53 -08:00
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
use solana_sdk::account::{create_account, Account};
|
2020-12-17 22:38:28 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
fn process<'a>(
|
2021-02-02 18:42:53 -08:00
|
|
|
program_id: &Pubkey,
|
2021-02-03 01:59:21 -08:00
|
|
|
ix: instruction::Instruction,
|
|
|
|
accounts: &'a [AccountInfo<'a>],
|
2020-12-17 22:38:28 -08:00
|
|
|
) -> ProgramResult {
|
2021-02-03 17:50:04 -08:00
|
|
|
let input = ix
|
|
|
|
.try_to_vec()
|
|
|
|
.map_err(|_| ProgramError::InvalidAccountData)?;
|
2021-02-03 01:59:21 -08:00
|
|
|
Processor::process(&program_id, accounts, &input)
|
2020-12-17 22:38:28 -08:00
|
|
|
}
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
fn rent_sysvar() -> TSysAccount {
|
|
|
|
TSysAccount(sysvar::rent::id(), create_account(&Rent::default(), 42))
|
2020-12-17 22:38:28 -08:00
|
|
|
}
|
|
|
|
|
2021-02-03 20:12:17 -08:00
|
|
|
fn sysclock(time: i64) -> TSysAccount {
|
|
|
|
let mut clock = Clock::default();
|
2021-02-17 05:38:29 -08:00
|
|
|
clock.slot = time as u64;
|
2021-02-03 20:12:17 -08:00
|
|
|
TSysAccount(sysvar::clock::id(), create_account(&clock, 42))
|
2020-12-17 22:38:28 -08:00
|
|
|
}
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
fn rent_exempt_balance(space: usize) -> u64 {
|
|
|
|
Rent::default().minimum_balance(space)
|
2020-12-17 22:38:28 -08:00
|
|
|
}
|
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
struct TAccount {
|
|
|
|
is_signer: bool,
|
|
|
|
pubkey: Pubkey,
|
|
|
|
account: Account,
|
|
|
|
}
|
2020-12-17 22:38:28 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
impl TAccount {
|
|
|
|
fn new(program_id: &Pubkey, is_signer: bool) -> TAccount {
|
|
|
|
TAccount {
|
|
|
|
is_signer,
|
|
|
|
pubkey: Pubkey::new_unique(),
|
2021-02-03 17:50:04 -08:00
|
|
|
account: Account::new(0, 0, &program_id),
|
2021-02-03 01:59:21 -08:00
|
|
|
}
|
|
|
|
}
|
2021-02-02 18:42:53 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
fn new_rent_exempt(program_id: &Pubkey, space: usize, is_signer: bool) -> TAccount {
|
|
|
|
TAccount {
|
|
|
|
is_signer,
|
|
|
|
pubkey: Pubkey::new_unique(),
|
2021-02-03 17:50:04 -08:00
|
|
|
account: Account::new(rent_exempt_balance(space), space, &program_id),
|
2021-02-03 01:59:21 -08:00
|
|
|
}
|
|
|
|
}
|
2020-12-17 22:38:28 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
fn info(&mut self) -> AccountInfo {
|
|
|
|
AccountInfo::new(
|
|
|
|
&self.pubkey,
|
|
|
|
self.is_signer,
|
|
|
|
false,
|
|
|
|
&mut self.account.lamports,
|
|
|
|
&mut self.account.data,
|
|
|
|
&self.account.owner,
|
|
|
|
self.account.executable,
|
|
|
|
self.account.rent_epoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Into<AccountInfo<'a>> for &'a mut TAccount {
|
|
|
|
fn into(self) -> AccountInfo<'a> {
|
|
|
|
self.info()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-03 17:50:04 -08:00
|
|
|
struct TSysAccount(Pubkey, Account);
|
2021-02-03 01:59:21 -08:00
|
|
|
impl<'a> Into<AccountInfo<'a>> for &'a mut TSysAccount {
|
|
|
|
fn into(self) -> AccountInfo<'a> {
|
|
|
|
AccountInfo::new(
|
|
|
|
&self.0,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
&mut self.1.lamports,
|
|
|
|
&mut self.1.data,
|
|
|
|
&self.1.owner,
|
|
|
|
self.1.executable,
|
|
|
|
self.1.rent_epoch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
struct TAggregator {
|
|
|
|
aggregator: TAccount,
|
|
|
|
aggregator_owner: TAccount,
|
|
|
|
round_submissions: TAccount,
|
|
|
|
answer_submissions: TAccount,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_aggregator(program_id: &Pubkey) -> Result<TAggregator, ProgramError> {
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut rent_sysvar = rent_sysvar();
|
2021-02-03 17:50:04 -08:00
|
|
|
let mut aggregator = TAccount::new_rent_exempt(
|
|
|
|
&program_id,
|
|
|
|
borsh_utils::get_packed_len::<Aggregator>(),
|
|
|
|
false,
|
|
|
|
);
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut aggregator_owner = TAccount::new(&program_id, true);
|
2021-02-05 00:33:48 -08:00
|
|
|
let mut round_submissions = TAccount::new_rent_exempt(
|
|
|
|
&program_id,
|
|
|
|
borsh_utils::get_packed_len::<Submissions>(),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
let mut answer_submissions = TAccount::new_rent_exempt(
|
|
|
|
&program_id,
|
|
|
|
borsh_utils::get_packed_len::<Submissions>(),
|
|
|
|
false,
|
|
|
|
);
|
2020-12-17 22:38:28 -08:00
|
|
|
|
2021-02-02 18:42:53 -08:00
|
|
|
process(
|
2021-01-08 09:39:16 -08:00
|
|
|
&program_id,
|
2021-02-03 01:59:21 -08:00
|
|
|
instruction::Instruction::Initialize {
|
2021-02-03 17:50:04 -08:00
|
|
|
config: AggregatorConfig {
|
2021-02-03 03:33:45 -08:00
|
|
|
decimals: 8,
|
2021-02-03 01:59:21 -08:00
|
|
|
description: [0u8; 32],
|
2021-02-03 20:12:17 -08:00
|
|
|
min_submissions: 2,
|
|
|
|
max_submissions: 2,
|
|
|
|
restart_delay: 1,
|
2021-02-04 01:43:00 -08:00
|
|
|
reward_amount: 10,
|
2021-02-03 20:12:17 -08:00
|
|
|
|
2021-02-03 03:33:45 -08:00
|
|
|
..AggregatorConfig::default()
|
2021-02-03 17:50:04 -08:00
|
|
|
},
|
2021-02-03 01:59:21 -08:00
|
|
|
},
|
2020-12-17 22:38:28 -08:00
|
|
|
vec![
|
2021-02-03 01:59:21 -08:00
|
|
|
(&mut rent_sysvar).into(),
|
|
|
|
(&mut aggregator).into(),
|
|
|
|
(&mut aggregator_owner).into(),
|
2021-02-05 00:33:48 -08:00
|
|
|
(&mut round_submissions).into(),
|
|
|
|
(&mut answer_submissions).into(),
|
2021-02-03 17:50:04 -08:00
|
|
|
]
|
|
|
|
.as_slice(),
|
2021-02-02 18:42:53 -08:00
|
|
|
)?;
|
2020-12-17 22:38:28 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
Ok(TAggregator {
|
|
|
|
aggregator,
|
|
|
|
aggregator_owner,
|
|
|
|
round_submissions,
|
|
|
|
answer_submissions,
|
|
|
|
})
|
2021-02-03 01:59:21 -08:00
|
|
|
}
|
|
|
|
|
2021-02-03 20:12:17 -08:00
|
|
|
fn create_oracle(
|
|
|
|
program_id: &Pubkey,
|
|
|
|
aggregator: &mut TAccount,
|
|
|
|
aggregator_owner: &mut TAccount,
|
|
|
|
) -> Result<(TAccount, TAccount), ProgramError> {
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut rent_sysvar = rent_sysvar();
|
2021-02-03 17:50:04 -08:00
|
|
|
let mut oracle =
|
|
|
|
TAccount::new_rent_exempt(&program_id, borsh_utils::get_packed_len::<Oracle>(), false);
|
2021-02-03 01:59:21 -08:00
|
|
|
let mut oracle_owner = TAccount::new(&program_id, true);
|
2021-02-02 18:42:53 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
process(
|
|
|
|
&program_id,
|
|
|
|
instruction::Instruction::AddOracle {
|
|
|
|
description: [0xab; 32],
|
|
|
|
},
|
|
|
|
vec![
|
|
|
|
(&mut rent_sysvar).into(),
|
2021-02-03 20:12:17 -08:00
|
|
|
aggregator.into(),
|
|
|
|
aggregator_owner.into(),
|
2021-02-03 01:59:21 -08:00
|
|
|
(&mut oracle).into(),
|
|
|
|
(&mut oracle_owner).into(),
|
2021-02-03 17:50:04 -08:00
|
|
|
]
|
|
|
|
.as_slice(),
|
2021-02-03 01:59:21 -08:00
|
|
|
)?;
|
|
|
|
|
2021-02-03 20:12:17 -08:00
|
|
|
Ok((oracle, oracle_owner))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_intialize() -> ProgramResult {
|
|
|
|
let program_id = Pubkey::new_unique();
|
|
|
|
create_aggregator(&program_id)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-04 01:56:09 -08:00
|
|
|
#[test]
|
|
|
|
fn test_configure() -> ProgramResult {
|
|
|
|
let program_id = Pubkey::new_unique();
|
2021-02-05 00:33:48 -08:00
|
|
|
let TAggregator {
|
|
|
|
mut aggregator,
|
|
|
|
mut aggregator_owner,
|
|
|
|
..
|
|
|
|
} = create_aggregator(&program_id)?;
|
2021-02-04 01:56:09 -08:00
|
|
|
|
|
|
|
process(
|
|
|
|
&program_id,
|
|
|
|
instruction::Instruction::Configure {
|
|
|
|
config: AggregatorConfig {
|
|
|
|
reward_amount: 1000,
|
|
|
|
..AggregatorConfig::default()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
vec![(&mut aggregator).into(), (&mut aggregator_owner).into()].as_slice(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let aggregator_state = Aggregator::load_initialized(&aggregator.info())?;
|
|
|
|
assert_eq!(aggregator_state.config.reward_amount, 1000);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-03 20:12:17 -08:00
|
|
|
#[test]
|
|
|
|
fn test_add_and_remove_oracle() -> ProgramResult {
|
|
|
|
let program_id = Pubkey::new_unique();
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let TAggregator {
|
|
|
|
mut aggregator,
|
|
|
|
mut aggregator_owner,
|
|
|
|
..
|
|
|
|
} = create_aggregator(&program_id)?;
|
2021-02-04 00:22:45 -08:00
|
|
|
let (mut oracle, _oracle_owner) =
|
2021-02-04 00:17:08 -08:00
|
|
|
create_oracle(&program_id, &mut aggregator, &mut aggregator_owner)?;
|
2021-02-03 20:12:17 -08:00
|
|
|
|
2021-02-03 01:59:21 -08:00
|
|
|
process(
|
|
|
|
&program_id,
|
|
|
|
instruction::Instruction::RemoveOracle {},
|
|
|
|
vec![
|
|
|
|
(&mut aggregator).into(),
|
|
|
|
(&mut aggregator_owner).into(),
|
|
|
|
(&mut oracle).into(),
|
2021-02-03 17:50:04 -08:00
|
|
|
]
|
|
|
|
.as_slice(),
|
2021-02-03 01:59:21 -08:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// println!("{}", hex::encode(oracle.account.data));
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-02 18:42:53 -08:00
|
|
|
|
2021-02-03 20:12:17 -08:00
|
|
|
struct SubmitTestFixture {
|
|
|
|
program_id: Pubkey,
|
2021-02-05 00:33:48 -08:00
|
|
|
t_aggregator: TAggregator,
|
2021-02-03 20:12:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SubmitTestFixture {
|
2021-02-04 00:17:08 -08:00
|
|
|
fn submit(
|
|
|
|
&mut self,
|
|
|
|
oracle: &mut TAccount,
|
|
|
|
oracle_owner: &mut TAccount,
|
|
|
|
time: u64,
|
|
|
|
round_id: u64,
|
|
|
|
value: u64,
|
|
|
|
) -> Result<Aggregator, ProgramError> {
|
2021-02-03 20:12:17 -08:00
|
|
|
let mut clock = sysclock(time as i64);
|
|
|
|
|
|
|
|
process(
|
|
|
|
&self.program_id,
|
|
|
|
instruction::Instruction::Submit { round_id, value },
|
|
|
|
vec![
|
|
|
|
(&mut clock).into(),
|
2021-02-05 00:33:48 -08:00
|
|
|
self.t_aggregator.aggregator.info(),
|
|
|
|
self.t_aggregator.round_submissions.info(),
|
|
|
|
self.t_aggregator.answer_submissions.info(),
|
2021-02-03 20:12:17 -08:00
|
|
|
oracle.into(),
|
|
|
|
oracle_owner.into(),
|
|
|
|
]
|
|
|
|
.as_slice(),
|
|
|
|
)?;
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
Aggregator::load_initialized(&self.t_aggregator.aggregator.info())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn aggregator(&mut self) -> Result<Aggregator, ProgramError> {
|
|
|
|
Aggregator::load_initialized(&self.t_aggregator.aggregator.info())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_oracle(&mut self) -> Result<(TAccount, TAccount), ProgramError> {
|
|
|
|
create_oracle(
|
|
|
|
&self.program_id,
|
|
|
|
&mut self.t_aggregator.aggregator,
|
|
|
|
&mut self.t_aggregator.aggregator_owner,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn round_submission(&mut self, i: usize) -> Result<Submission, ProgramError> {
|
|
|
|
Ok(self.round_submissions()?.data[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn round_submissions(&mut self) -> Result<Submissions, ProgramError> {
|
|
|
|
self.aggregator()?
|
|
|
|
.round_submissions(&self.t_aggregator.round_submissions.info())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn answer_submission(&mut self, i: usize) -> Result<Submission, ProgramError> {
|
|
|
|
Ok(self.answer_submissions()?.data[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
fn answer_submissions(&mut self) -> Result<Submissions, ProgramError> {
|
|
|
|
self.aggregator()?
|
|
|
|
.answer_submissions(&self.t_aggregator.answer_submissions.info())
|
2021-02-03 20:12:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn test_submit() -> ProgramResult {
|
|
|
|
let program_id = Pubkey::new_unique();
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let mut tt = SubmitTestFixture {
|
2021-02-03 20:12:17 -08:00
|
|
|
program_id,
|
2021-02-05 00:33:48 -08:00
|
|
|
t_aggregator: create_aggregator(&program_id)?,
|
2021-02-03 20:12:17 -08:00
|
|
|
};
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let (mut oracle, mut oracle_owner) = tt.create_oracle()?;
|
|
|
|
let (mut oracle2, mut oracle_owner2) = tt.create_oracle()?;
|
|
|
|
let (mut oracle3, mut oracle_owner3) = tt.create_oracle()?;
|
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
let time = 100;
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle, &mut oracle_owner, time, 0, 1)?;
|
2021-02-04 01:43:00 -08:00
|
|
|
let oracle_state = Oracle::load_initialized(&oracle.info())?;
|
2021-02-05 00:33:48 -08:00
|
|
|
let sub = tt.round_submission(0)?;
|
|
|
|
let round = &agr.round;
|
2021-02-04 01:43:00 -08:00
|
|
|
assert_eq!(oracle_state.withdrawable, 10);
|
2021-02-04 19:00:19 -08:00
|
|
|
assert_eq!(round.created_at, time);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.updated_at, time);
|
2021-02-03 20:12:17 -08:00
|
|
|
assert_eq!(sub.oracle, oracle.pubkey.to_bytes());
|
|
|
|
assert_eq!(sub.value, 1);
|
|
|
|
assert_eq!(sub.updated_at, 100);
|
|
|
|
assert_eq!(agr.answer.is_initialized(), false);
|
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
// test: should fail with repeated submission
|
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.submit(&mut oracle, &mut oracle_owner, time + 10, 0, 2)
|
2021-02-04 00:17:08 -08:00
|
|
|
.map_err(Error::from),
|
2021-02-03 22:50:12 -08:00
|
|
|
Err(Error::OracleAlreadySubmitted),
|
|
|
|
"should fail if oracle submits repeatedly in the same round"
|
|
|
|
);
|
|
|
|
|
|
|
|
let old_time = time;
|
|
|
|
let time = 200;
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle2, &mut oracle_owner2, time, 0, 2)?;
|
2021-02-04 01:43:00 -08:00
|
|
|
let oracle_state = Oracle::load_initialized(&oracle.info())?;
|
2021-02-05 00:33:48 -08:00
|
|
|
let sub = tt.round_submission(1)?;
|
|
|
|
let round = &agr.round;
|
2021-02-04 01:43:00 -08:00
|
|
|
assert_eq!(oracle_state.withdrawable, 10);
|
2021-02-04 19:00:19 -08:00
|
|
|
assert_eq!(round.created_at, old_time);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.updated_at, time);
|
2021-02-03 20:12:17 -08:00
|
|
|
assert_eq!(sub.oracle, oracle2.pubkey.to_bytes());
|
|
|
|
assert_eq!(sub.value, 2);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(sub.updated_at, time);
|
2021-02-03 20:12:17 -08:00
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
// // test: answer resolved when min_submissions is reached
|
2021-02-03 20:12:17 -08:00
|
|
|
let answer = &agr.answer;
|
|
|
|
assert_eq!(answer.is_initialized(), true);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(answer.updated_at, time);
|
|
|
|
assert_eq!(answer.created_at, time);
|
2021-02-05 00:33:48 -08:00
|
|
|
|
|
|
|
let answer_submissions = tt.answer_submissions()?;
|
|
|
|
let round_submissions = tt.round_submissions()?;
|
|
|
|
assert_eq!(answer_submissions, round_submissions);
|
2021-02-03 20:12:17 -08:00
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
// test: max submission reached
|
2021-02-03 20:12:17 -08:00
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.submit(&mut oracle3, &mut oracle_owner3, time + 10, 0, 2)
|
2021-02-04 00:17:08 -08:00
|
|
|
.map_err(Error::from),
|
2021-02-03 22:50:12 -08:00
|
|
|
Err(Error::MaxSubmissionsReached),
|
2021-02-03 20:12:17 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
// test: start new round
|
2021-02-03 22:50:12 -08:00
|
|
|
let time = 300;
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle, &mut oracle_owner, time, 1, 10)?;
|
2021-02-04 01:43:00 -08:00
|
|
|
let oracle_state = Oracle::load_initialized(&oracle.info())?;
|
2021-02-05 00:33:48 -08:00
|
|
|
let sub = tt.round_submission(0)?;
|
|
|
|
let round = &agr.round;
|
2021-02-04 01:43:00 -08:00
|
|
|
assert_eq!(oracle_state.withdrawable, 20);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.id, 1);
|
2021-02-04 19:00:19 -08:00
|
|
|
assert_eq!(round.created_at, time);
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.updated_at, time);
|
|
|
|
assert_eq!(sub.oracle, oracle.pubkey.to_bytes());
|
|
|
|
assert_eq!(sub.value, 10);
|
|
|
|
assert_eq!(sub.updated_at, time);
|
2021-02-04 00:17:08 -08:00
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.round_submission(1)?.is_initialized(),
|
2021-02-04 00:17:08 -08:00
|
|
|
false,
|
|
|
|
"other submissions should've been zero after starting a new round"
|
|
|
|
);
|
2021-02-03 22:50:12 -08:00
|
|
|
|
|
|
|
// the last round answer should be reserved
|
|
|
|
let answer = &agr.answer;
|
|
|
|
assert_eq!(answer.is_initialized(), true);
|
|
|
|
assert_eq!(answer.round_id, 0);
|
|
|
|
assert_eq!(answer.updated_at, 200);
|
|
|
|
assert_eq!(answer.created_at, 200);
|
|
|
|
|
|
|
|
// test: oracle cannot immediately start a new round
|
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.submit(&mut oracle, &mut oracle_owner, time + 10, 2, 2)
|
2021-02-04 00:17:08 -08:00
|
|
|
.map_err(Error::from),
|
2021-02-03 22:50:12 -08:00
|
|
|
Err(Error::OracleNewRoundCooldown),
|
|
|
|
);
|
|
|
|
|
|
|
|
// test: resolve a new round
|
|
|
|
let time = 400;
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle2, &mut oracle_owner2, time, 1, 20)?;
|
|
|
|
let sub = tt.round_submission(1)?;
|
2021-02-03 22:50:12 -08:00
|
|
|
|
|
|
|
assert_eq!(sub.oracle, oracle2.pubkey.to_bytes());
|
|
|
|
assert_eq!(sub.value, 20);
|
|
|
|
assert_eq!(sub.updated_at, time);
|
|
|
|
|
|
|
|
let answer = &agr.answer;
|
|
|
|
assert_eq!(answer.is_initialized(), true);
|
|
|
|
assert_eq!(answer.round_id, 1);
|
|
|
|
assert_eq!(answer.updated_at, time);
|
|
|
|
assert_eq!(answer.created_at, time);
|
2021-02-17 19:12:50 -08:00
|
|
|
assert_eq!(answer.median, 15);
|
2021-02-05 00:33:48 -08:00
|
|
|
|
|
|
|
assert_eq!(tt.answer_submission(0)?.value, 10);
|
|
|
|
assert_eq!(tt.answer_submission(1)?.value, 20);
|
2021-02-03 22:50:12 -08:00
|
|
|
|
|
|
|
let time = 500;
|
|
|
|
// let oracle 2 start a new round
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle2, &mut oracle_owner2, time, 2, 200)?;
|
|
|
|
let round = &agr.round;
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.id, 2);
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle, &mut oracle_owner, time, 3, 200)?;
|
|
|
|
let round = &agr.round;
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.id, 3);
|
|
|
|
|
2021-02-05 00:33:48 -08:00
|
|
|
let agr = tt.submit(&mut oracle2, &mut oracle_owner2, time, 4, 200)?;
|
|
|
|
let round = &agr.round;
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(round.id, 4);
|
|
|
|
|
|
|
|
// InvalidRoundID
|
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.submit(&mut oracle, &mut oracle_owner, time + 10, 10, 1000)
|
2021-02-04 00:17:08 -08:00
|
|
|
.map_err(Error::from),
|
2021-02-03 22:50:12 -08:00
|
|
|
Err(Error::InvalidRoundID),
|
|
|
|
"should only be able to start a round with current_round.id + 1"
|
|
|
|
);
|
2021-02-03 20:12:17 -08:00
|
|
|
|
2021-02-03 22:50:12 -08:00
|
|
|
assert_eq!(
|
2021-02-05 00:33:48 -08:00
|
|
|
tt.submit(&mut oracle3, &mut oracle_owner3, time + 10, 3, 1000)
|
2021-02-04 00:17:08 -08:00
|
|
|
.map_err(Error::from),
|
2021-02-03 22:50:12 -08:00
|
|
|
Err(Error::InvalidRoundID),
|
|
|
|
"should not be able to submit answer to previous rounds"
|
|
|
|
);
|
2021-02-03 20:12:17 -08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-01-08 09:39:16 -08:00
|
|
|
}
|