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
#[allow(unaligned_references)]
use super::common::Hash;
use super::decimal::SwitchboardDecimal;
use super::error::SwitchboardError;
use anchor_lang::prelude::*;
use anchor_lang::AnchorDeserialize;

use solana_program::pubkey::Pubkey;

#[zero_copy]
#[derive(AnchorDeserialize, Default, Debug, PartialEq, Eq)]
pub struct AggregatorRound {
    // Maintains the number of successful responses received from nodes.
    // Nodes can submit one successful response per round.
    pub num_success: u32,
    pub num_error: u32,
    pub is_closed: bool,
    // Maintains the `solana_program::clock::Slot` that the round was opened at.
    pub round_open_slot: u64,
    // Maintains the `solana_program::clock::UnixTimestamp;` the round was opened at.
    pub round_open_timestamp: i64,
    // Maintains the current median of all successful round responses.
    pub result: SwitchboardDecimal,
    // Standard deviation of the accepted results in the round.
    pub std_deviation: SwitchboardDecimal,
    // Maintains the minimum node response this round.
    pub min_response: SwitchboardDecimal,
    // Maintains the maximum node response this round.
    pub max_response: SwitchboardDecimal,
    // pub lease_key: Option<Pubkey>,
    // Pubkeys of the oracles fulfilling this round.
    pub oracle_pubkeys_data: [Pubkey; 16],
    // pub oracle_pubkeys_size: Option<u32>, IMPLIED BY ORACLE_REQUEST_BATCH_SIZE
    // Represents all successful node responses this round. `NaN` if empty.
    pub medians_data: [SwitchboardDecimal; 16],
    // Current rewards/slashes oracles have received this round.
    pub current_payout: [i64; 16],
    // Optionals do not work on zero_copy. Keep track of which responses are
    // fulfilled here.
    pub medians_fulfilled: [bool; 16],
    // could do specific error codes
    pub errors_fulfilled: [bool; 16],
}
impl AggregatorRound {
    pub fn is_round_valid(&self, min_oracle_results: u32) -> bool {
        if self.num_success >= min_oracle_results {
            return true;
        }
        false
    }
}

#[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,
    // CONFIGS
    // affects update price, shouldnt be changeable
    pub oracle_request_batch_size: u32,
    pub min_oracle_results: u32,
    pub min_job_results: u32,
    // affects update price, shouldnt be changeable
    pub min_update_delay_seconds: u32,
    // timestamp to start feed updates at
    pub start_after: i64,
    pub variance_threshold: SwitchboardDecimal,
    // If no feed results after this period, trigger nodes to report
    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,
    // Used to confirm with oracles they are answering what they think theyre answering
    pub jobs_checksum: [u8; 32],
    //
    pub authority: Pubkey,
    pub _ebuf: [u8; 224], // Buffer for future info
}

impl AggregatorAccountData {
    pub fn get_result(&self) -> Result<AggregatorRound, ProgramError> {
        if self.current_round.is_round_valid(self.min_oracle_results) {
            Ok(self.current_round)
        } else if self
            .latest_confirmed_round
            .is_round_valid(self.min_oracle_results)
        {
            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 {};
//         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))
//         );
//     }
// }