solana-flux-aggregator/program/src/processor.rs

692 lines
22 KiB
Rust
Raw Normal View History

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-04 00:43:14 -08:00
instruction::Instruction,
2021-02-03 17:50:04 -08:00
state::{Aggregator, AggregatorConfig, Oracle, Round},
};
2020-11-27 22:52:54 -08:00
2020-11-26 14:19:50 -08:00
use solana_program::{
account_info::{next_account_info, AccountInfo},
2021-01-08 09:39:16 -08:00
clock::Clock,
2020-11-26 14:19:50 -08:00
entrypoint::ProgramResult,
2021-01-08 09:39:16 -08:00
msg,
program::invoke_signed,
program_error::ProgramError,
2021-02-03 17:50:04 -08:00
program_pack::IsInitialized,
2020-11-26 14:19:50 -08:00
pubkey::Pubkey,
sysvar::{rent::Rent, Sysvar},
};
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-02 18:42:53 -08:00
struct Accounts<'a, 'b>(&'a [AccountInfo<'b>]);
2020-11-26 14:19:50 -08:00
2021-02-03 17:50:04 -08:00
impl<'a, 'b> Accounts<'a, 'b> {
2021-02-02 18:42:53 -08:00
fn get(&self, i: usize) -> Result<&'a AccountInfo<'b>, 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>,
owner: &'a AccountInfo<'a>,
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 {
if !self.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-03 01:59:21 -08:00
let mut aggregator = Aggregator::init_uninitialized(self.aggregator)?;
aggregator.is_initialized = true;
aggregator.config = self.config.clone();
aggregator.owner = self.owner.key.to_bytes();
aggregator.save_exempt(self.aggregator, &self.rent)?;
2020-11-27 22:52:54 -08:00
2021-02-03 01:59:21 -08:00
Ok(())
}
}
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
if !self.aggregator_owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
2021-01-10 18:02:48 -08:00
}
2020-11-27 22:52:54 -08:00
2021-02-03 01:59:21 -08:00
let aggregator = Aggregator::load_initialized(self.aggregator)?;
if aggregator.owner != self.aggregator_owner.key.to_bytes() {
return Err(Error::OwnerMismatch)?;
}
2020-12-04 04:11:10 -08:00
2021-02-03 01:59:21 -08:00
let mut oracle = Oracle::init_uninitialized(self.oracle)?;
oracle.is_initialized = true;
oracle.description = self.description;
oracle.owner = self.oracle_owner.key.to_bytes();
oracle.aggregator = self.aggregator.key.to_bytes();
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 {
if !self.aggregator_owner.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let aggregator = Aggregator::load_initialized(self.aggregator)?;
if aggregator.owner != self.aggregator_owner.key.to_bytes() {
return Err(Error::OwnerMismatch)?;
}
let oracle = Oracle::load_initialized(self.oracle)?;
if oracle.aggregator != 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>,
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)?;
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)?;
}
2021-02-03 22:50:12 -08:00
self.submit(&mut aggregator)?;
2021-02-03 03:33:45 -08:00
// 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.
2021-02-03 22:50:12 -08:00
fn submit(&self, aggregator: &mut Aggregator) -> ProgramResult {
2021-02-03 03:33:45 -08:00
let now = self.clock.unix_timestamp as u64;
2021-02-03 17:50:04 -08:00
let (i, submission) = aggregator
.current_round
.submissions
.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-03 22:50:12 -08:00
if aggregator.current_round.started_at == 0 {
aggregator.current_round.started_at = now;
}
aggregator.current_round.updated_at = now;
2021-02-03 03:33:45 -08:00
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;
2021-02-03 22:50:12 -08:00
// let new_answer = !answer.submissions[0].is_initialized();
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;
answer.submissions = round.submissions;
} else {
answer.updated_at = now;
answer.submissions[i] = new_submission;
}
Ok(())
}
2021-02-03 17:50:04 -08:00
fn start_new_round(&self, aggregator: &mut Aggregator, oracle: &mut Oracle) -> ProgramResult {
2021-02-03 03:33:45 -08:00
let now = self.clock.unix_timestamp as u64;
2021-02-04 00:17:08 -08:00
if aggregator.current_round.id < oracle.allow_start_round {
2021-02-03 03:33:45 -08:00
return Err(Error::OracleNewRoundCooldown)?;
}
// zero the submissions of the current round
aggregator.current_round = Round {
id: self.round_id,
started_at: now,
..Round::default()
};
2021-02-03 22:50:12 -08:00
// oracle can start new round after `restart_delay` rounds
oracle.allow_start_round = self.round_id + aggregator.config.restart_delay;
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.
struct WithdrawContext<'a> {
token: &'a AccountInfo<'a>,
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-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)?;
2021-02-02 18:42:53 -08:00
match instruction {
2021-02-03 17:50:04 -08:00
Instruction::Initialize { config } => InitializeContext {
rent: accounts.get_rent(0)?,
aggregator: accounts.get(1)?,
owner: accounts.get(2)?,
2021-02-03 01:59:21 -08:00
config,
}
2021-02-03 17:50:04 -08:00
.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)?,
}
.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(),
2021-02-03 22:50:12 -08:00
_ => Err(ProgramError::InvalidInstructionData),
2021-02-04 00:17:08 -08:00
// Instruction::Withdraw { amount, seed } => {
// msg!("Instruction: Withdraw");
// Self::process_withdraw(accounts, amount, seed)
// }
2020-12-01 02:50:31 -08:00
}
2020-11-27 22:52:54 -08:00
}
2020-11-30 09:41:09 -08:00
}
2020-11-26 14:19:50 -08:00
#[cfg(test)]
mod tests {
use super::*;
2021-02-02 18:42:53 -08:00
2021-02-03 01:59:21 -08:00
use crate::borsh_utils;
2021-02-02 18:42:53 -08:00
use crate::instruction;
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};
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>],
) -> 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)
}
2021-02-03 01:59:21 -08:00
fn rent_sysvar() -> TSysAccount {
TSysAccount(sysvar::rent::id(), create_account(&Rent::default(), 42))
}
2021-02-03 20:12:17 -08:00
fn sysclock(time: i64) -> TSysAccount {
let mut clock = Clock::default();
clock.unix_timestamp = time;
TSysAccount(sysvar::clock::id(), create_account(&clock, 42))
}
2021-02-03 01:59:21 -08:00
fn rent_exempt_balance(space: usize) -> u64 {
Rent::default().minimum_balance(space)
}
2021-02-03 01:59:21 -08:00
struct TAccount {
is_signer: bool,
pubkey: Pubkey,
account: Account,
}
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
}
}
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-03 20:12:17 -08:00
fn create_aggregator(program_id: &Pubkey) -> 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 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-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-03 03:33:45 -08:00
..AggregatorConfig::default()
2021-02-03 17:50:04 -08:00
},
2021-02-03 01:59:21 -08:00
},
vec![
2021-02-03 01:59:21 -08:00
(&mut rent_sysvar).into(),
(&mut aggregator).into(),
(&mut aggregator_owner).into(),
2021-02-03 17:50:04 -08:00
]
.as_slice(),
2021-02-02 18:42:53 -08:00
)?;
2021-02-03 01:59:21 -08:00
Ok((aggregator, aggregator_owner))
}
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(())
}
#[test]
fn test_add_and_remove_oracle() -> ProgramResult {
let program_id = Pubkey::new_unique();
let (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,
aggregator: TAccount,
aggregator_owner: TAccount,
}
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(),
self.aggregator.info(),
oracle.into(),
oracle_owner.into(),
]
.as_slice(),
)?;
Aggregator::load_initialized(&self.aggregator.info())
}
}
#[test]
fn test_submit() -> ProgramResult {
let program_id = Pubkey::new_unique();
let (mut aggregator, mut aggregator_owner) = create_aggregator(&program_id)?;
2021-02-04 00:17:08 -08:00
let (mut oracle, mut oracle_owner) =
create_oracle(&program_id, &mut aggregator, &mut aggregator_owner)?;
let (mut oracle2, mut oracle_owner2) =
create_oracle(&program_id, &mut aggregator, &mut aggregator_owner)?;
let (mut oracle3, mut oracle_owner3) =
create_oracle(&program_id, &mut aggregator, &mut aggregator_owner)?;
2021-02-03 20:12:17 -08:00
let mut fixture = SubmitTestFixture {
program_id,
aggregator,
aggregator_owner,
};
2021-02-03 22:50:12 -08:00
let time = 100;
let agr = fixture.submit(&mut oracle, &mut oracle_owner, time, 0, 1)?;
let sub = &agr.current_round.submissions[0];
let round = &agr.current_round;
assert_eq!(round.started_at, time);
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-04 00:17:08 -08:00
fixture
.submit(&mut oracle, &mut oracle_owner, time + 10, 0, 2)
.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;
let agr = fixture.submit(&mut oracle2, &mut oracle_owner2, time, 0, 2)?;
let sub = &agr.current_round.submissions[1];
let round = &agr.current_round;
assert_eq!(round.started_at, old_time);
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
// test: answer resolved when min_submissions is reached
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-03 20:12:17 -08:00
assert_eq!(answer.submissions, agr.current_round.submissions);
2021-02-03 22:50:12 -08:00
// test: max submission reached
2021-02-03 20:12:17 -08:00
assert_eq!(
2021-02-04 00:17:08 -08:00
fixture
.submit(&mut oracle3, &mut oracle_owner3, time + 10, 0, 2)
.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;
let agr = fixture.submit(&mut oracle, &mut oracle_owner, time, 1, 10)?;
let sub = &agr.current_round.submissions[0];
let round = &agr.current_round;
assert_eq!(round.id, 1);
assert_eq!(round.started_at, time);
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!(
round.submissions[1].is_initialized(),
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-04 00:17:08 -08:00
fixture
.submit(&mut oracle, &mut oracle_owner, time + 10, 2, 2)
.map_err(Error::from),
2021-02-03 22:50:12 -08:00
Err(Error::OracleNewRoundCooldown),
);
// test: resolve a new round
let time = 400;
let agr = fixture.submit(&mut oracle2, &mut oracle_owner2, time, 1, 20)?;
let sub = &agr.current_round.submissions[1];
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);
assert_eq!(answer.submissions[0].value, 10);
assert_eq!(answer.submissions[1].value, 20);
let time = 500;
// let oracle 2 start a new round
let agr = fixture.submit(&mut oracle2, &mut oracle_owner2, time, 2, 200)?;
let round = &agr.current_round;
assert_eq!(round.id, 2);
let agr = fixture.submit(&mut oracle, &mut oracle_owner, time, 3, 200)?;
let round = &agr.current_round;
assert_eq!(round.id, 3);
let agr = fixture.submit(&mut oracle2, &mut oracle_owner2, time, 4, 200)?;
let round = &agr.current_round;
assert_eq!(round.id, 4);
// InvalidRoundID
assert_eq!(
2021-02-04 00:17:08 -08:00
fixture
.submit(&mut oracle, &mut oracle_owner, time + 10, 10, 1000)
.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-04 00:17:08 -08:00
fixture
.submit(&mut oracle3, &mut oracle_owner3, time + 10, 3, 1000)
.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
}