1use wincode::{SchemaRead, SchemaWrite};
2
3#[repr(u8)]
5#[derive(Clone, Copy, Debug, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead)]
6#[wincode(tag_encoding = "u8")]
7pub enum OracleKind {
8 #[wincode(tag = 0)]
9 Switchboard = 0,
10 #[wincode(tag = 1)]
11 Pyth = 1,
12}
13
14impl OracleKind {
15 pub const fn as_u8(self) -> u8 {
16 self as u8
17 }
18
19 pub const fn from_u8(kind: u8) -> Option<Self> {
20 match kind {
21 0 => Some(Self::Switchboard),
22 1 => Some(Self::Pyth),
23 _ => None,
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub struct InvalidOracleKind;
30
31#[derive(
36 Clone, Copy, Debug, Default, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead,
37)]
38#[wincode(assert_zero_copy)]
39#[repr(C)]
40pub struct SwitchboardOracleConfig {
41 pub quote_account: [u8; 32],
42 pub queue_account: [u8; 32],
43 pub feed_id: [u8; 32],
44 pub max_age_slots: u64,
45 pub price_decimals: u8,
46 _padding: [u8; 7],
47}
48
49impl SwitchboardOracleConfig {
50 pub const fn new(
51 quote_account: [u8; 32],
52 queue_account: [u8; 32],
53 feed_id: [u8; 32],
54 price_decimals: u8,
55 max_age_slots: u64,
56 ) -> Self {
57 Self {
58 quote_account,
59 queue_account,
60 feed_id,
61 max_age_slots,
62 price_decimals,
63 _padding: [0; 7],
64 }
65 }
66}
67
68#[derive(
77 Clone, Copy, Debug, Default, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead,
78)]
79#[wincode(assert_zero_copy)]
80#[repr(C)]
81pub struct PythOracleConfig {
82 pub feed_id: [u8; 32],
83 pub max_age_seconds: u64,
84 pub max_confidence_bps: u16,
85 pub price_decimals: u8,
86 _padding: [u8; 5],
87}
88
89impl PythOracleConfig {
90 pub const fn new(
91 feed_id: [u8; 32],
92 price_decimals: u8,
93 max_age_seconds: u64,
94 max_confidence_bps: u16,
95 ) -> Self {
96 Self {
97 feed_id,
98 max_age_seconds,
99 max_confidence_bps,
100 price_decimals,
101 _padding: [0; 5],
102 }
103 }
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead)]
112#[wincode(assert_zero_copy)]
113#[repr(C)]
114pub struct OracleConfig {
115 pub switchboard: SwitchboardOracleConfig,
116 pub pyth: PythOracleConfig,
117 kind: u8,
118 _padding: [u8; 7],
119}
120
121impl OracleConfig {
122 pub const fn raw_kind(&self) -> u8 {
123 self.kind
124 }
125
126 pub const fn kind(&self) -> Result<OracleKind, InvalidOracleKind> {
127 match OracleKind::from_u8(self.kind) {
128 Some(kind) => Ok(kind),
129 None => Err(InvalidOracleKind),
130 }
131 }
132
133 pub const fn validate(&self) -> Result<(), InvalidOracleKind> {
134 match self.kind() {
135 Ok(_) => Ok(()),
136 Err(error) => Err(error),
137 }
138 }
139
140 pub const fn switchboard(config: SwitchboardOracleConfig) -> Self {
141 Self {
142 switchboard: config,
143 pyth: PythOracleConfig {
144 feed_id: [0; 32],
145 max_age_seconds: 0,
146 max_confidence_bps: 0,
147 price_decimals: 0,
148 _padding: [0; 5],
149 },
150 kind: OracleKind::Switchboard.as_u8(),
151 _padding: [0; 7],
152 }
153 }
154
155 pub const fn pyth(config: PythOracleConfig) -> Self {
156 Self {
157 switchboard: SwitchboardOracleConfig {
158 quote_account: [0; 32],
159 queue_account: [0; 32],
160 feed_id: [0; 32],
161 max_age_slots: 0,
162 price_decimals: 0,
163 _padding: [0; 7],
164 },
165 pyth: config,
166 kind: OracleKind::Pyth.as_u8(),
167 _padding: [0; 7],
168 }
169 }
170
171 pub const fn with_configs(
172 kind: OracleKind,
173 switchboard: SwitchboardOracleConfig,
174 pyth: PythOracleConfig,
175 ) -> Self {
176 Self {
177 switchboard,
178 pyth,
179 kind: kind.as_u8(),
180 _padding: [0; 7],
181 }
182 }
183}
184
185impl Default for OracleConfig {
186 fn default() -> Self {
187 Self::switchboard(SwitchboardOracleConfig::default())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use wincode::{config::DefaultConfig, serialize, SchemaRead, SchemaWrite, TypeMeta};
195
196 fn assert_zero_copy<T>()
197 where
198 T: wincode::ZeroCopy,
199 T: for<'de> SchemaRead<'de, DefaultConfig> + SchemaWrite<DefaultConfig>,
200 {
201 assert_eq!(
202 <T as SchemaRead<'_, DefaultConfig>>::TYPE_META,
203 TypeMeta::Static {
204 size: core::mem::size_of::<T>(),
205 zero_copy: true,
206 }
207 );
208 assert_eq!(
209 <T as SchemaWrite<DefaultConfig>>::TYPE_META,
210 TypeMeta::Static {
211 size: core::mem::size_of::<T>(),
212 zero_copy: true,
213 }
214 );
215 }
216
217 #[test]
218 fn oracle_config_size_is_fixed_across_implementations() {
219 let switchboard = OracleConfig::switchboard(SwitchboardOracleConfig::new(
220 [1; 32], [2; 32], [3; 32], 6, 100,
221 ));
222 let pyth = OracleConfig::pyth(PythOracleConfig::new([4; 32], 8, 30, 250));
223
224 assert_eq!(
225 serialize(&switchboard).unwrap().len(),
226 serialize(&pyth).unwrap().len()
227 );
228 assert_eq!(switchboard.kind(), Ok(OracleKind::Switchboard));
229 assert_eq!(pyth.kind(), Ok(OracleKind::Pyth));
230 }
231
232 #[test]
233 fn with_configs_keeps_inactive_config_available() {
234 let switchboard_config = SwitchboardOracleConfig::new([1; 32], [2; 32], [3; 32], 6, 100);
235 let pyth_config = PythOracleConfig::new([4; 32], 8, 30, 250);
236
237 let config = OracleConfig::with_configs(OracleKind::Pyth, switchboard_config, pyth_config);
238
239 assert_eq!(config.kind(), Ok(OracleKind::Pyth));
240 assert_eq!(config.switchboard, switchboard_config);
241 assert_eq!(config.pyth, pyth_config);
242 }
243
244 #[test]
245 fn oracle_configs_are_zero_copy() {
246 assert_zero_copy::<SwitchboardOracleConfig>();
247 assert_zero_copy::<PythOracleConfig>();
248 assert_zero_copy::<OracleConfig>();
249 assert_eq!(core::mem::size_of::<SwitchboardOracleConfig>(), 112);
250 assert_eq!(core::mem::size_of::<PythOracleConfig>(), 48);
251 assert_eq!(core::mem::size_of::<OracleConfig>(), 168);
252 assert_eq!(
253 serialize(&OracleConfig::default()).unwrap().len(),
254 core::mem::size_of::<OracleConfig>()
255 );
256 }
257
258 #[test]
259 fn oracle_config_rejects_invalid_kind() {
260 let config = OracleConfig {
261 kind: 255,
262 ..OracleConfig::default()
263 };
264
265 assert_eq!(config.kind(), Err(InvalidOracleKind));
266 assert_eq!(config.validate(), Err(InvalidOracleKind));
267 }
268}