1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use super::common::Hash;
use super::decimal::SwitchboardDecimal;
use super::error::SwitchboardError;
use anchor_lang::AnchorDeserialize;
use anchor_lang::prelude::*;
use solana_program::pubkey::Pubkey;
#[zero_copy]
#[derive(AnchorDeserialize, Default, Debug, PartialEq, Eq)]
pub struct AggregatorRound {
pub num_success: u32,
pub num_error: u32,
pub is_closed: bool,
pub round_open_slot: u64,
pub round_open_timestamp: i64,
pub result: SwitchboardDecimal,
pub std_deviation: SwitchboardDecimal,
pub min_response: SwitchboardDecimal,
pub max_response: SwitchboardDecimal,
pub oracle_pubkeys_data: [Pubkey; 16],
pub medians_data: [SwitchboardDecimal; 16],
pub current_payout: [i64; 16],
pub medians_fulfilled: [bool; 16],
pub errors_fulfilled: [bool; 16],
}
impl AggregatorRound {
pub fn is_round_valid(&self, min_oracle_results: u32) -> Result<bool, ProgramError> {
if self.num_success < min_oracle_results {
return Ok(false);
}
Ok(true)
}
}
impl Default for AggregatorAccountData {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
#[account(zero_copy)]
#[derive(AnchorDeserialize, Debug, PartialEq)]
pub struct AggregatorAccountData {
pub name: [u8; 32],
pub metadata: [u8; 128],
pub author_wallet: Pubkey,
pub queue_pubkey: Pubkey,
pub oracle_request_batch_size: u32,
pub min_oracle_results: u32,
pub min_job_results: u32,
pub min_update_delay_seconds: u32,
pub start_after: i64,
pub variance_threshold: SwitchboardDecimal,
pub force_report_period: i64,
pub expiration: i64,
pub consecutive_failure_count: u64,
pub next_allowed_update_time: i64,
pub is_locked: bool,
pub _schedule: [u8; 32],
pub latest_confirmed_round: AggregatorRound,
pub current_round: AggregatorRound,
pub job_pubkeys_data: [Pubkey; 16],
pub job_hashes: [Hash; 16],
pub job_pubkeys_size: u32,
pub jobs_checksum: [u8; 32],
pub authority: Pubkey,
pub _ebuf: [u8; 224],
}
impl AggregatorAccountData {
pub fn new(switchboard_feed: &AccountInfo) -> Result<AggregatorAccountData, ProgramError> {
let aggregator_account_loader =
AccountLoader::<AggregatorAccountData>::try_from(switchboard_feed)?;
let aggregator: AggregatorAccountData = *aggregator_account_loader.load()?;
Ok(aggregator)
}
pub fn is_current_round_valid(&self) -> Result<bool, ProgramError> {
let round = self.current_round.clone();
round.is_round_valid(self.min_oracle_results)
}
pub fn is_latest_confirmed_round_valid(&self) -> Result<bool, ProgramError> {
let round = self.latest_confirmed_round.clone();
round.is_round_valid(self.min_oracle_results)
}
pub fn get_result(&self) -> Result<AggregatorRound, ProgramError> {
if self.is_current_round_valid().unwrap() {
Ok(self.current_round)
} else if self.is_latest_confirmed_round_valid().unwrap() {
Ok(self.latest_confirmed_round)
} else {
Err(ProgramError::from(SwitchboardError::InvalidAggregatorRound))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_aggregator(
current_round: AggregatorRound,
last_round: AggregatorRound,
) -> AggregatorAccountData {
let mut aggregator = AggregatorAccountData::default();
aggregator.min_update_delay_seconds = 10;
aggregator.latest_confirmed_round = last_round;
aggregator.current_round = current_round;
aggregator.min_job_results = 10;
aggregator.min_oracle_results = 10;
return aggregator;
}
fn create_round(num_success: u32, num_error: u32, v: f64) -> AggregatorRound {
let mut result = AggregatorRound::default();
result.num_success = num_success;
result.num_error = num_error;
result.result = SwitchboardDecimal::from_f64(v);
return result;
}
#[test]
fn test_reject_current_on_sucess_count() {
let current_round = create_round(2, 5, 97.5);
let last_round = create_round(30, 0, 100.0);
let aggregator = create_aggregator(current_round.clone(), last_round.clone());
assert_eq!(aggregator.get_result().unwrap(), last_round.clone());
}
#[test]
fn test_accept_current_on_sucess_count() {
let current_round = create_round(20, 5, 97.5);
let last_round = create_round(30, 0, 100.0);
let aggregator = create_aggregator(current_round.clone(), last_round.clone());
assert_eq!(aggregator.get_result().unwrap(), current_round.clone());
}
#[test]
fn test_no_valid_aggregator_result() {
let current_round = create_round(1, 5, 97.5);
let last_round = create_round(1, 5, 100.0);
let aggregator = create_aggregator(current_round.clone(), last_round.clone());
assert_eq!(
aggregator.get_result(),
Err(ProgramError::from(SwitchboardError::InvalidAggregatorRound))
);
}
}