use wincode::{SchemaRead, SchemaWrite};
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead)]
#[wincode(tag_encoding = "u8")]
pub enum OracleKind {
#[wincode(tag = 0)]
Switchboard = 0,
#[wincode(tag = 1)]
Pyth = 1,
}
impl OracleKind {
pub const fn as_u8(self) -> u8 {
self as u8
}
pub const fn from_u8(kind: u8) -> Option<Self> {
match kind {
0 => Some(Self::Switchboard),
1 => Some(Self::Pyth),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidOracleKind;
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead,
)]
#[wincode(assert_zero_copy)]
#[repr(C)]
pub struct SwitchboardOracleConfig {
pub quote_account: [u8; 32],
pub queue_account: [u8; 32],
pub feed_id: [u8; 32],
pub max_age_slots: u64,
pub price_decimals: u8,
_padding: [u8; 7],
}
impl SwitchboardOracleConfig {
pub const fn new(
quote_account: [u8; 32],
queue_account: [u8; 32],
feed_id: [u8; 32],
price_decimals: u8,
max_age_slots: u64,
) -> Self {
Self {
quote_account,
queue_account,
feed_id,
max_age_slots,
price_decimals,
_padding: [0; 7],
}
}
}
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead,
)]
#[wincode(assert_zero_copy)]
#[repr(C)]
pub struct PythOracleConfig {
pub feed_id: [u8; 32],
pub max_age_seconds: u64,
pub max_confidence_bps: u16,
pub price_decimals: u8,
_padding: [u8; 5],
}
impl PythOracleConfig {
pub const fn new(
feed_id: [u8; 32],
price_decimals: u8,
max_age_seconds: u64,
max_confidence_bps: u16,
) -> Self {
Self {
feed_id,
max_age_seconds,
max_confidence_bps,
price_decimals,
_padding: [0; 5],
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, codama_macros::CodamaType, SchemaWrite, SchemaRead)]
#[wincode(assert_zero_copy)]
#[repr(C)]
pub struct OracleConfig {
pub switchboard: SwitchboardOracleConfig,
pub pyth: PythOracleConfig,
kind: u8,
_padding: [u8; 7],
}
impl OracleConfig {
pub const fn raw_kind(&self) -> u8 {
self.kind
}
pub const fn kind(&self) -> Result<OracleKind, InvalidOracleKind> {
match OracleKind::from_u8(self.kind) {
Some(kind) => Ok(kind),
None => Err(InvalidOracleKind),
}
}
pub const fn validate(&self) -> Result<(), InvalidOracleKind> {
match self.kind() {
Ok(_) => Ok(()),
Err(error) => Err(error),
}
}
pub const fn switchboard(config: SwitchboardOracleConfig) -> Self {
Self {
switchboard: config,
pyth: PythOracleConfig {
feed_id: [0; 32],
max_age_seconds: 0,
max_confidence_bps: 0,
price_decimals: 0,
_padding: [0; 5],
},
kind: OracleKind::Switchboard.as_u8(),
_padding: [0; 7],
}
}
pub const fn pyth(config: PythOracleConfig) -> Self {
Self {
switchboard: SwitchboardOracleConfig {
quote_account: [0; 32],
queue_account: [0; 32],
feed_id: [0; 32],
max_age_slots: 0,
price_decimals: 0,
_padding: [0; 7],
},
pyth: config,
kind: OracleKind::Pyth.as_u8(),
_padding: [0; 7],
}
}
pub const fn with_configs(
kind: OracleKind,
switchboard: SwitchboardOracleConfig,
pyth: PythOracleConfig,
) -> Self {
Self {
switchboard,
pyth,
kind: kind.as_u8(),
_padding: [0; 7],
}
}
}
impl Default for OracleConfig {
fn default() -> Self {
Self::switchboard(SwitchboardOracleConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use wincode::{config::DefaultConfig, serialize, SchemaRead, SchemaWrite, TypeMeta};
fn assert_zero_copy<T>()
where
T: wincode::ZeroCopy,
T: for<'de> SchemaRead<'de, DefaultConfig> + SchemaWrite<DefaultConfig>,
{
assert_eq!(
<T as SchemaRead<'_, DefaultConfig>>::TYPE_META,
TypeMeta::Static {
size: core::mem::size_of::<T>(),
zero_copy: true,
}
);
assert_eq!(
<T as SchemaWrite<DefaultConfig>>::TYPE_META,
TypeMeta::Static {
size: core::mem::size_of::<T>(),
zero_copy: true,
}
);
}
#[test]
fn oracle_config_size_is_fixed_across_implementations() {
let switchboard = OracleConfig::switchboard(SwitchboardOracleConfig::new(
[1; 32], [2; 32], [3; 32], 6, 100,
));
let pyth = OracleConfig::pyth(PythOracleConfig::new([4; 32], 8, 30, 250));
assert_eq!(
serialize(&switchboard).unwrap().len(),
serialize(&pyth).unwrap().len()
);
assert_eq!(switchboard.kind(), Ok(OracleKind::Switchboard));
assert_eq!(pyth.kind(), Ok(OracleKind::Pyth));
}
#[test]
fn with_configs_keeps_inactive_config_available() {
let switchboard_config = SwitchboardOracleConfig::new([1; 32], [2; 32], [3; 32], 6, 100);
let pyth_config = PythOracleConfig::new([4; 32], 8, 30, 250);
let config = OracleConfig::with_configs(OracleKind::Pyth, switchboard_config, pyth_config);
assert_eq!(config.kind(), Ok(OracleKind::Pyth));
assert_eq!(config.switchboard, switchboard_config);
assert_eq!(config.pyth, pyth_config);
}
#[test]
fn oracle_configs_are_zero_copy() {
assert_zero_copy::<SwitchboardOracleConfig>();
assert_zero_copy::<PythOracleConfig>();
assert_zero_copy::<OracleConfig>();
assert_eq!(core::mem::size_of::<SwitchboardOracleConfig>(), 112);
assert_eq!(core::mem::size_of::<PythOracleConfig>(), 48);
assert_eq!(core::mem::size_of::<OracleConfig>(), 168);
assert_eq!(
serialize(&OracleConfig::default()).unwrap().len(),
core::mem::size_of::<OracleConfig>()
);
}
#[test]
fn oracle_config_rejects_invalid_kind() {
let config = OracleConfig {
kind: 255,
..OracleConfig::default()
};
assert_eq!(config.kind(), Err(InvalidOracleKind));
assert_eq!(config.validate(), Err(InvalidOracleKind));
}
}