use std::{
collections::BTreeMap,
convert::Infallible,
fmt,
ops::Deref,
str::FromStr,
sync::Arc,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "non-pdk")]
use clap::Subcommand;
use rialo_cli_representable::Representable;
use rialo_limits::{max_oracle_output_serialized_bytes, MIN_VIABLE_LIMIT_OF_ORACLE_OUTPUT_SIZE};
use rialo_s_compute_budget::compute_budget_limits::{MAX_COMPUTE_UNIT_LIMIT, MAX_HEAP_FRAME_BYTES};
use rialo_s_pubkey::Pubkey;
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
#[cfg(feature = "non-pdk")]
use url::Url;
use crate::{AttestationReport, AuthorityKeyBytes, Headers, HttpFilter, Nonce, OracleDutyConfig};
pub type TimestampMs = u64;
const MIN_UPDATE_PERIOD_MS: TimestampMs = 50;
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
)]
pub struct OracleId {
pub nonce: Nonce,
pub creator: Pubkey,
}
impl OracleId {
pub fn new(creator: Pubkey, nonce: impl Into<Nonce>) -> Self {
Self {
nonce: nonce.into(),
creator,
}
}
}
impl fmt::Display for OracleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", &self.nonce, &self.creator)
}
}
impl FromStr for OracleId {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).map_err(|e| format!("Failed to parse OracleId: {}", e))
}
}
#[cfg(feature = "non-pdk")]
fn parse_oracle_id(s: &str) -> Result<OracleId, String> {
OracleId::from_str(s)
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Representable)]
#[representable(human_readable = "oracle_info_human_readable")]
pub struct OracleInfo {
pub id: OracleId,
pub description: String,
pub update_frequency: UpdateFrequency,
pub target_oracles: Vec<TargetOracle>,
pub starting_timestamp: StartingTimestamp,
pub is_active: bool,
pub created_at_ms: i64,
#[serde(default = "default_validators_per_duty")]
pub validators_per_duty: u32,
#[serde(default = "default_oracle_request_delay_ms")]
pub oracle_request_delay_ms: TimestampMs,
pub compute_units_limit: Option<u32>,
pub heap_size_limit: Option<u32>,
}
fn oracle_info_human_readable(info: &OracleInfo) -> String {
let mut out = String::new();
out.push_str(&format!("Oracle ID: {}\n", info.id));
out.push_str(&format!("Description: {}\n", info.description));
out.push_str(&format!("Active: {}\n", info.is_active));
out.push_str(&format!(
"Starting Timestamp: {:?}\n",
info.starting_timestamp
));
out.push_str(&format!("Update Frequency: {:?}\n", info.update_frequency));
out.push_str(&format!("Created At: {}\n", info.created_at_ms));
out.push_str(&format!(
"Validators Per Duty: {}\n",
info.validators_per_duty
));
out.push_str(&format!(
"Oracle Request Delay: {}\n",
info.oracle_request_delay_ms
));
if !info.target_oracles.is_empty() {
out.push_str(&format!(
"\nTarget Oracles ({}):\n",
info.target_oracles.len()
));
for (i, target) in info.target_oracles.iter().enumerate() {
out.push_str(&format!(" {}. {:?}\n", i + 1, target));
}
}
if let Some(compute_units) = info.compute_units_limit {
out.push_str(&format!("\nCompute Units Limit: {}\n", compute_units));
}
if let Some(heap_size) = info.heap_size_limit {
out.push_str(&format!("Heap Size Limit: {}\n", heap_size));
}
out
}
impl Default for OracleInfo {
fn default() -> Self {
Self {
id: OracleId::default(),
description: String::new(),
update_frequency: UpdateFrequency::default(),
target_oracles: Vec::new(),
starting_timestamp: StartingTimestamp::default(),
is_active: false,
created_at_ms: 0,
validators_per_duty: default_validators_per_duty(),
oracle_request_delay_ms: default_oracle_request_delay_ms(),
compute_units_limit: None,
heap_size_limit: None,
}
}
}
impl OracleInfo {
pub fn is_asap(&self) -> bool {
matches!(self.starting_timestamp, StartingTimestamp::Asap)
}
pub fn target_timestamp(&self) -> Option<TimestampMs> {
match self.starting_timestamp {
StartingTimestamp::Timestamp(timestamp) => Some(timestamp),
StartingTimestamp::Asap => None,
}
}
pub fn validate(&self) -> Result<(), String> {
match self.starting_timestamp {
StartingTimestamp::Asap => {
if !matches!(self.update_frequency, UpdateFrequency::OneShot) {
return Err("ASAP oracles cannot be periodic".to_string());
}
}
StartingTimestamp::Timestamp(starting_timestamp) => {
match self.update_frequency {
UpdateFrequency::OneShot => {}
UpdateFrequency::Periodic(period)
| UpdateFrequency::LimitedPeriodic(period, _) => {
validate_periodic_frequency(period)?;
if let UpdateFrequency::LimitedPeriodic(_, end_timestamp) =
self.update_frequency
{
if starting_timestamp >= end_timestamp {
return Err("end_timestamp of a LimitedPeriodic oracle should be above starting_timestamp".to_string());
}
}
}
}
}
}
if self.target_oracles.is_empty() {
return Err("OracleTargets cannot be empty".to_string());
}
if self.oracle_request_delay_ms < OracleDutyConfig::MIN_ORACLE_REQUEST_DELAY {
return Err(format!(
"oracle_request_delay cannot be below {}",
OracleDutyConfig::MIN_ORACLE_REQUEST_DELAY
));
}
if self.oracle_request_delay_ms > OracleDutyConfig::MAX_ORACLE_REQUEST_DELAY_MS {
return Err(format!(
"oracle_request_delay cannot be above {}",
OracleDutyConfig::MAX_ORACLE_REQUEST_DELAY_MS
));
}
if self.validators_per_duty == 0 {
return Err("validators_per_duty cannot be 0".to_string());
}
let max_oracle_output_size = max_oracle_output_serialized_bytes(self.validators_per_duty);
if max_oracle_output_size < MIN_VIABLE_LIMIT_OF_ORACLE_OUTPUT_SIZE {
return Err(format!("validators_per_duty is too high, results in max size of oracle updates that is too low: {max_oracle_output_size} vs {MIN_VIABLE_LIMIT_OF_ORACLE_OUTPUT_SIZE}"));
}
if let Some(compute_units_limit) = self.compute_units_limit {
if compute_units_limit == 0 {
return Err("compute_usage_limit cannot be Some(0)".to_string());
}
if compute_units_limit > MAX_COMPUTE_UNIT_LIMIT {
return Err(format!("compute_usage_limit cannot be above MAX_COMPUTE_UNIT_LIMIT={MAX_COMPUTE_UNIT_LIMIT}"));
}
}
if let Some(heap_size_limit) = self.heap_size_limit {
if heap_size_limit == 0 {
return Err("heap_size_limit cannot be Some(0)".to_string());
}
if heap_size_limit > MAX_HEAP_FRAME_BYTES {
return Err(format!(
"heap_size_limit cannot be above MAX_HEAP_FRAME_BYTES={MAX_HEAP_FRAME_BYTES}"
));
}
}
Ok(())
}
pub fn websocket_op(&self) -> Option<WebSocketOperation> {
self.target_oracles
.first()
.and_then(|target| target.websocket_op())
}
}
fn validate_periodic_frequency(period_ms: TimestampMs) -> Result<(), String> {
if period_ms == 0 {
return Err("update frequency cannot be zero".to_string());
}
if period_ms < MIN_UPDATE_PERIOD_MS {
return Err(format!(
"update frequency {period_ms} cannot be below {MIN_UPDATE_PERIOD_MS}"
));
}
Ok(())
}
impl TargetOracle {
pub fn websocket_op(&self) -> Option<WebSocketOperation> {
if let TargetOracle::WebSocket(ws_op) = self {
Some(ws_op.clone())
} else {
None
}
}
}
fn default_validators_per_duty() -> u32 {
OracleDutyConfig::DEFAULT_VALIDATORS_PER_DUTY
}
fn default_oracle_request_delay_ms() -> TimestampMs {
OracleDutyConfig::DEFAULT_ORACLE_REQUEST_DELAY_MS
}
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct OracleEntry {
oracle_info: Arc<OracleInfo>,
data_hash: [u8; OracleEntry::HASH_LENGTH],
last_modified_timestamp: u64,
}
impl OracleEntry {
const HASH_LENGTH: usize = 32;
pub fn new(
oracle_info: OracleInfo,
data_hash: [u8; Self::HASH_LENGTH],
last_modified_round: u64,
) -> Self {
Self {
oracle_info: Arc::new(oracle_info),
data_hash,
last_modified_timestamp: last_modified_round,
}
}
pub fn oracle_info(&self) -> Arc<OracleInfo> {
self.oracle_info.clone()
}
pub fn last_modified_timestamp(&self) -> u64 {
self.last_modified_timestamp
}
pub fn data_hash(&self) -> &[u8; Self::HASH_LENGTH] {
&self.data_hash
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum OracleValue {
Plain(String),
Encrypted(String),
}
impl Default for OracleValue {
fn default() -> Self {
OracleValue::Plain(String::new())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct OracleUrl(OracleValue);
impl fmt::Display for OracleUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
OracleValue::Plain(ref s) => write!(f, "{}", s),
OracleValue::Encrypted(ref s) => write!(f, "enc://{}", s),
}
}
}
impl Deref for OracleUrl {
type Target = OracleValue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "non-pdk")]
impl From<Url> for OracleUrl {
fn from(url: Url) -> Self {
url.to_string().into()
}
}
#[cfg(feature = "non-pdk")]
impl From<&Url> for OracleUrl {
fn from(url: &Url) -> Self {
Self(OracleValue::Plain(url.to_string()))
}
}
impl From<String> for OracleUrl {
fn from(url: String) -> Self {
url.as_str().into()
}
}
impl From<&str> for OracleUrl {
fn from(s: &str) -> Self {
if let Some(encrypted) = s.strip_prefix("enc://") {
Self(OracleValue::Encrypted(encrypted.into()))
} else {
Self(OracleValue::Plain(s.into()))
}
}
}
impl FromStr for OracleUrl {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum OracleValueBody {
Plain(Vec<u8>),
Encrypted(Vec<u8>),
}
impl Default for OracleValueBody {
fn default() -> Self {
OracleValueBody::Plain(vec![])
}
}
impl FromStr for OracleValueBody {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(OracleValueBody::Plain(s.as_bytes().to_vec()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum_macros::AsRefStr)]
#[cfg_attr(feature = "non-pdk", derive(Subcommand))]
pub enum TargetOracle {
HttpGet {
#[cfg_attr(feature = "non-pdk", clap(
long = "target-url",
value_parser = clap::value_parser!(OracleUrl)
))]
url: OracleUrl,
#[cfg_attr(feature = "non-pdk", clap(long, default_value = None))]
filter: Option<Vec<HttpFilter>>,
#[cfg_attr(feature = "non-pdk", clap(long, default_value_t = Headers::default()))]
headers: Headers,
},
HttpPost {
#[cfg_attr(feature = "non-pdk", clap(
long = "target-url",
value_parser = clap::value_parser!(OracleUrl)
))]
url: OracleUrl,
#[cfg_attr(feature = "non-pdk", clap(long))]
filter: Option<Vec<HttpFilter>>,
#[cfg_attr(feature = "non-pdk", clap(
long,
value_parser = clap::value_parser!(OracleValueBody)
))]
body: OracleValueBody,
#[cfg_attr(feature = "non-pdk", clap(long))]
content_type: String,
#[cfg_attr(feature = "non-pdk", clap(long, default_value_t = Headers::default()))]
headers: Headers,
},
Time,
PriceReactor,
Number,
SecretKeyGeneration {
#[cfg_attr(feature = "non-pdk", clap(long))]
committee_id: String,
#[cfg_attr(feature = "non-pdk", clap(long))]
committee_members: Vec<String>,
},
SecretKeyEncryption {
#[cfg_attr(feature = "non-pdk", clap(long))]
target_tee_id: String,
#[cfg_attr(feature = "non-pdk", clap(long))]
secret_data: Vec<u8>,
#[cfg_attr(feature = "non-pdk", clap(long))]
committee_id: String,
},
SecretKeyDecryption {
#[cfg_attr(feature = "non-pdk", clap(long))]
encrypted_data: Vec<u8>,
#[cfg_attr(feature = "non-pdk", clap(long))]
source_committee_id: String,
},
Stonks,
#[cfg_attr(feature = "non-pdk", clap(subcommand))]
WebSocket(WebSocketOperation),
}
impl TargetOracle {
pub fn is_websocket(&self) -> bool {
matches!(self, TargetOracle::WebSocket(_))
}
}
pub const SYSTEM_MESSAGE_SIZE: usize = 128;
#[derive(
Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub enum WebSocketReadMode {
#[default]
Latest,
All,
FromIndex(u64),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub enum MessageContent {
Data(Vec<u8>),
OversizedWarning {
original_size: usize,
limit: usize,
original_timestamp: String,
},
}
impl MessageContent {
pub fn byte_size(&self) -> usize {
match self {
MessageContent::Data(data) => data.len(),
MessageContent::OversizedWarning { .. } => SYSTEM_MESSAGE_SIZE,
}
}
pub fn is_system(&self) -> bool {
matches!(self, MessageContent::OversizedWarning { .. })
}
pub fn as_data(&self) -> Option<&[u8]> {
match self {
MessageContent::Data(data) => Some(data),
MessageContent::OversizedWarning { .. } => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct BufferedMessage {
pub index: u64,
pub content: MessageContent,
pub received_at: String,
}
impl BufferedMessage {
pub fn byte_size(&self) -> usize {
self.content.byte_size()
}
pub fn is_system_message(&self) -> bool {
self.content.is_system()
}
pub fn as_data(&self) -> Option<&[u8]> {
self.content.as_data()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct WebSocketReadResponse {
pub messages: Vec<BufferedMessage>,
pub latest_index: Option<u64>,
pub oldest_index: Option<u64>,
}
impl WebSocketReadResponse {
pub fn has_messages(&self) -> bool {
!self.messages.is_empty()
}
pub fn message_count(&self) -> usize {
self.messages.len()
}
pub fn first_message(&self) -> Option<&BufferedMessage> {
self.messages.first()
}
pub fn latest_message(&self) -> Option<&BufferedMessage> {
self.messages.last()
}
pub fn first_data(&self) -> Option<&[u8]> {
self.messages.first().and_then(|m| m.as_data())
}
pub fn latest_data(&self) -> Option<&[u8]> {
self.messages.last().and_then(|m| m.as_data())
}
pub fn data_messages(&self) -> impl Iterator<Item = &BufferedMessage> {
self.messages.iter().filter(|m| !m.is_system_message())
}
pub fn iter_data(&self) -> impl Iterator<Item = &[u8]> {
self.messages.iter().filter_map(|m| m.as_data())
}
pub fn data_message_count(&self) -> usize {
self.messages
.iter()
.filter(|m| !m.is_system_message())
.count()
}
pub fn has_system_messages(&self) -> bool {
self.messages.iter().any(|m| m.is_system_message())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "non-pdk", derive(Subcommand))]
pub enum WebSocketOperation {
Connect {
#[cfg_attr(feature = "non-pdk", clap(
long = "target-url",
value_parser = clap::value_parser!(OracleUrl)
))]
url: OracleUrl,
#[cfg_attr(feature = "non-pdk", clap(long, value_parser = parse_oracle_id))]
oracle_id: OracleId,
},
Read {
#[cfg_attr(feature = "non-pdk", clap(long, value_parser = parse_oracle_id))]
connection_oracle_id: OracleId,
#[serde(default)]
#[cfg_attr(feature = "non-pdk", clap(skip))]
mode: WebSocketReadMode,
},
Send {
#[cfg_attr(feature = "non-pdk", clap(long, value_parser = parse_oracle_id))]
connection_oracle_id: OracleId,
#[cfg_attr(feature = "non-pdk", clap(skip))]
messages: Vec<OracleValue>,
},
Close {
#[cfg_attr(feature = "non-pdk", clap(long, value_parser = parse_oracle_id))]
connection_oracle_id: OracleId,
},
}
impl WebSocketOperation {
pub fn connect(url: impl Into<OracleUrl>, oracle_id: OracleId) -> Self {
WebSocketOperation::Connect {
url: url.into(),
oracle_id,
}
}
pub fn read(connection_oracle_id: OracleId) -> Self {
WebSocketOperation::Read {
connection_oracle_id,
mode: WebSocketReadMode::default(),
}
}
pub fn read_with_mode(connection_oracle_id: OracleId, mode: WebSocketReadMode) -> Self {
WebSocketOperation::Read {
connection_oracle_id,
mode,
}
}
pub fn send(connection_oracle_id: OracleId, messages: Vec<OracleValue>) -> Self {
WebSocketOperation::Send {
connection_oracle_id,
messages,
}
}
pub fn close(connection_oracle_id: OracleId) -> Self {
WebSocketOperation::Close {
connection_oracle_id,
}
}
pub fn is_connect(&self) -> bool {
matches!(self, WebSocketOperation::Connect { .. })
}
pub fn is_read(&self) -> bool {
matches!(self, WebSocketOperation::Read { .. })
}
pub fn is_send(&self) -> bool {
matches!(self, WebSocketOperation::Send { .. })
}
pub fn is_close(&self) -> bool {
matches!(self, WebSocketOperation::Close { .. })
}
pub fn url(&self) -> Option<&OracleUrl> {
match self {
WebSocketOperation::Connect { url, .. } => Some(url),
_ => None,
}
}
pub fn oracle_id(&self) -> Option<&OracleId> {
match self {
WebSocketOperation::Connect { oracle_id, .. } => Some(oracle_id),
_ => None,
}
}
pub fn connection_oracle_id(&self) -> Option<&OracleId> {
match self {
WebSocketOperation::Read {
connection_oracle_id,
..
}
| WebSocketOperation::Send {
connection_oracle_id,
..
}
| WebSocketOperation::Close {
connection_oracle_id,
} => Some(connection_oracle_id),
_ => None,
}
}
pub fn read_mode(&self) -> Option<&WebSocketReadMode> {
match self {
WebSocketOperation::Read { mode, .. } => Some(mode),
_ => None,
}
}
pub fn messages(&self) -> Option<&[OracleValue]> {
match self {
WebSocketOperation::Send { messages, .. } => Some(messages),
_ => None,
}
}
}
impl fmt::Display for WebSocketOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WebSocketOperation::Connect { url, oracle_id } => {
write!(f, "Connect(url={url}, oracle_id={oracle_id})")
}
WebSocketOperation::Read {
connection_oracle_id,
mode,
} => {
write!(
f,
"Read(connection_oracle_id={}, mode={:?})",
connection_oracle_id, mode
)
}
WebSocketOperation::Send {
connection_oracle_id,
messages,
} => {
write!(
f,
"Send(connection_oracle_id={}, messages_count={})",
connection_oracle_id,
messages.len()
)
}
WebSocketOperation::Close {
connection_oracle_id,
} => {
write!(f, "Close(connection_oracle_id={})", connection_oracle_id)
}
}
}
}
impl FromStr for TargetOracle {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "Time" {
Ok(TargetOracle::Time)
} else if s == "PriceReactor" {
Ok(TargetOracle::PriceReactor)
} else if s == "SecretKeyGeneration" {
Err("SecretKeyGeneration oracle requires committee_id and committee_members parameters. Use the appropriate API to create this oracle type.".to_string())
} else if s == "SecretKeyEncryption" {
Err("SecretKeyEncryption oracle requires target_tee_id, secret_data, and committee_id parameters. Use the appropriate API to create this oracle type.".to_string())
} else if s == "SecretKeyDecryption" {
Err("SecretKeyDecryption oracle requires encrypted_data and source_committee_id parameters. Use the appropriate API to create this oracle type.".to_string())
} else if s == "number" {
Err("The 'number' oracle is only for testing purposes and should not be used in production.".to_string())
} else {
if let Some(rest) = s.strip_prefix("HttpGet:") {
let parts: Vec<&str> = rest.splitn(2, '|').collect();
if parts.is_empty() {
return Err(
"Invalid HttpGet format. Use 'HttpGet:<url>[|<filter>]'.".to_string()
);
}
let url = parts[0].to_string();
let filter = if parts.len() > 1 && !parts[1].is_empty() {
Some(vec![HttpFilter::from_str(parts[1])?])
} else {
None
};
#[cfg(feature = "non-pdk")]
if Url::parse(&url).is_err() {
return Err(format!("Invalid URL: {url}"));
}
return Ok(TargetOracle::HttpGet {
url: url.into(),
filter,
headers: Headers::default(),
});
}
Err(format!("Unknown TargetOracle type: {s}"))
}
}
}
pub type InputCommitmentBytes = [u8; 32];
pub type SignatureBytes = [u8; 64];
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OracleUpdateResult {
pub oracle_id: OracleId,
pub target_timestamp: TimestampMs,
#[serde(with = "BigArray")]
pub response_hash: [u8; 32],
#[serde(with = "BigArray")]
pub input_commitment: InputCommitmentBytes,
#[serde(with = "BigArray")]
pub signature: SignatureBytes,
pub oracle_result: Vec<u8>,
pub attestation_report: Option<AttestationReport>,
#[serde(with = "BigArray")]
pub authority_key: AuthorityKeyBytes,
}
impl OracleUpdateResult {
pub fn new(
oracle_id: OracleId,
target_timestamp: TimestampMs,
oracle_result: Vec<u8>,
input_commitment: InputCommitmentBytes,
signature: SignatureBytes,
attestation_report: Option<AttestationReport>,
authority_key: AuthorityKeyBytes,
) -> Result<Self, &'static str> {
let hash = blake3::hash(&oracle_result);
#[cfg(feature = "non-pdk")]
let oracle_result = if oracle_result.len() > rialo_limits::MAX_TRANSACTION_SIZE as usize {
tracing::error!(
"Oracle result size {} exceeds maximum size {}, dropping the result.",
oracle_result.len(),
rialo_limits::MAX_TRANSACTION_SIZE
);
return Err("Oracle result exceeds maximum size");
} else {
oracle_result
};
#[cfg(not(feature = "non-pdk"))]
let oracle_result = oracle_result;
Ok(Self {
oracle_id,
target_timestamp,
response_hash: *hash.as_bytes(),
input_commitment,
signature,
oracle_result,
attestation_report,
authority_key,
})
}
}
impl Default for OracleUpdateResult {
fn default() -> Self {
Self {
oracle_id: OracleId::default(),
target_timestamp: 0,
response_hash: [0; 32],
input_commitment: [0xee; 32],
signature: [0; 64],
oracle_result: vec![],
attestation_report: None,
authority_key: [0xff; 96],
}
}
}
#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
pub struct OracleRequest {
pub oracle_id: Option<OracleId>,
pub target_timestamp: Option<TimestampMs>,
pub authority_key: AuthorityKeyBytes,
pub include_attestation: bool,
pub max_oracle_output_size: u32,
pub params: BTreeMap<String, String>,
}
impl Default for OracleRequest {
fn default() -> Self {
Self {
oracle_id: None,
target_timestamp: None,
authority_key: [0; 96],
include_attestation: true,
max_oracle_output_size: 0,
params: BTreeMap::default(),
}
}
}
impl OracleRequest {
pub fn input_commitment(&self) -> Result<blake3::Hash, &'static str> {
let request_bytes = borsh::to_vec(self).map_err(|_| "Failed to serialize OracleRequest")?;
Ok(blake3::hash(&request_bytes))
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum UpdateFrequency {
#[default]
OneShot,
Periodic(TimestampMs),
LimitedPeriodic(TimestampMs, TimestampMs),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum StartingTimestamp {
Timestamp(TimestampMs),
Asap,
}
impl Default for StartingTimestamp {
fn default() -> Self {
StartingTimestamp::Timestamp(0)
}
}
impl UpdateFrequency {
pub fn periodic(duration: Duration) -> Self {
Self::Periodic(duration.as_millis() as TimestampMs)
}
}
impl StartingTimestamp {
pub fn start_offset(offset: Duration) -> Self {
let timestamp = SystemTime::now() + offset;
Self::Timestamp(timestamp.duration_since(UNIX_EPOCH).unwrap().as_millis() as TimestampMs)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base_valid_oracle_info() -> OracleInfo {
OracleInfo {
description: "test".to_string(),
target_oracles: vec![TargetOracle::Time],
update_frequency: UpdateFrequency::OneShot,
starting_timestamp: StartingTimestamp::Timestamp(0),
..OracleInfo::default()
}
}
#[test]
fn test_is_asap_true_and_false() {
let mut info = base_valid_oracle_info();
assert!(!info.is_asap());
info.starting_timestamp = StartingTimestamp::Asap;
assert!(info.is_asap());
}
#[test]
fn test_validate_success_minimal() {
let info = base_valid_oracle_info();
assert!(info.validate().is_ok());
}
#[test]
fn test_asap_cannot_be_periodic() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Asap;
info.update_frequency = UpdateFrequency::Periodic(10);
let err = info.validate().unwrap_err();
assert!(err.contains("ASAP oracles cannot be periodic"));
}
#[test]
fn test_asap_cannot_be_limited_periodic() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Asap;
info.update_frequency = UpdateFrequency::LimitedPeriodic(5, 100);
let err = info.validate().unwrap_err();
assert!(err.contains("ASAP oracles cannot be periodic"));
}
#[test]
fn test_periodic_with_zero_period_is_invalid() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Timestamp(1);
info.update_frequency = UpdateFrequency::Periodic(0);
let err = info.validate().unwrap_err();
assert!(err.contains("update frequency cannot be zero"));
}
#[test]
fn test_limited_periodic_with_zero_period_is_invalid() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Timestamp(1);
info.update_frequency = UpdateFrequency::LimitedPeriodic(0, 100);
let err = info.validate().unwrap_err();
assert!(err.contains("update frequency cannot be zero"));
}
#[test]
fn test_limited_periodic_end_timestamp_must_be_above_starting_timestamp() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Timestamp(500);
info.update_frequency = UpdateFrequency::LimitedPeriodic(300, 500);
let err = info.validate().unwrap_err();
assert!(err.contains(
"end_timestamp of a LimitedPeriodic oracle should be above starting_timestamp"
));
}
#[test]
fn test_limited_periodic_end_timestamp_below_starting_timestamp_is_invalid() {
let mut info = base_valid_oracle_info();
info.starting_timestamp = StartingTimestamp::Timestamp(1000);
info.update_frequency = UpdateFrequency::LimitedPeriodic(300, 900);
let err = info.validate().unwrap_err();
assert!(err.contains(
"end_timestamp of a LimitedPeriodic oracle should be above starting_timestamp"
));
}
#[test]
fn test_target_oracles_cannot_be_empty() {
let mut info = base_valid_oracle_info();
info.target_oracles.clear();
let err = info.validate().unwrap_err();
assert!(err.contains("OracleTargets cannot be empty"));
}
#[test]
fn test_oracle_request_delay_bounds() {
let mut info = base_valid_oracle_info();
info.oracle_request_delay_ms = OracleDutyConfig::MIN_ORACLE_REQUEST_DELAY - 1;
let err = info.validate().unwrap_err();
assert!(err.contains(&format!(
"oracle_request_delay cannot be below {}",
OracleDutyConfig::MIN_ORACLE_REQUEST_DELAY
)));
let mut info = base_valid_oracle_info();
info.oracle_request_delay_ms = OracleDutyConfig::MAX_ORACLE_REQUEST_DELAY_MS + 1;
let err = info.validate().unwrap_err();
assert!(err.contains(&format!(
"oracle_request_delay cannot be above {}",
OracleDutyConfig::MAX_ORACLE_REQUEST_DELAY_MS
)));
}
#[test]
fn test_validators_per_duty_cannot_be_zero() {
let mut info = base_valid_oracle_info();
info.validators_per_duty = 0;
let err = info.validate().unwrap_err();
assert!(err.contains("validators_per_duty cannot be 0"));
}
#[test]
fn test_validators_per_duty_too_high_results_in_too_low_output_size() {
let mut info = base_valid_oracle_info();
info.validators_per_duty = 1_000_000; let err = info.validate().unwrap_err();
assert!(err.contains("validators_per_duty is too high"));
}
#[test]
fn test_compute_units_limit_checks() {
let mut info = base_valid_oracle_info();
info.compute_units_limit = Some(0);
let err = info.validate().unwrap_err();
assert!(err.contains("compute_usage_limit cannot be Some(0)"));
let mut info = base_valid_oracle_info();
info.compute_units_limit = Some(MAX_COMPUTE_UNIT_LIMIT + 1);
let err = info.validate().unwrap_err();
assert!(err.contains(&format!(
"compute_usage_limit cannot be above MAX_COMPUTE_UNIT_LIMIT={}",
MAX_COMPUTE_UNIT_LIMIT
)));
}
#[test]
fn test_heap_size_limit_checks() {
let mut info = base_valid_oracle_info();
info.heap_size_limit = Some(0);
let err = info.validate().unwrap_err();
assert!(err.contains("heap_size_limit cannot be Some(0)"));
let mut info = base_valid_oracle_info();
info.heap_size_limit = Some(MAX_HEAP_FRAME_BYTES + 1);
let err = info.validate().unwrap_err();
assert!(err.contains(&format!(
"heap_size_limit cannot be above MAX_HEAP_FRAME_BYTES={}",
MAX_HEAP_FRAME_BYTES
)));
}
#[test]
fn test_websocket_connect_operation_creation() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let op = WebSocketOperation::connect("wss://example.com/stream", oracle_id);
assert!(op.is_connect());
assert!(!op.is_read());
if let WebSocketOperation::Connect {
url,
oracle_id: op_oracle_id,
} = op
{
assert_eq!(url.to_string(), "wss://example.com/stream");
assert_eq!(op_oracle_id, oracle_id);
} else {
panic!("Expected Connect variant");
}
}
#[test]
fn test_websocket_read_operation_creation() {
let oracle_id = OracleId::new(Pubkey::default(), 42u64);
let op = WebSocketOperation::read(oracle_id);
assert!(op.is_read());
assert!(!op.is_connect());
if let WebSocketOperation::Read {
connection_oracle_id,
mode,
} = op
{
assert_eq!(connection_oracle_id, oracle_id);
assert_eq!(mode, WebSocketReadMode::Latest);
} else {
panic!("Expected Read variant");
}
}
#[test]
fn test_websocket_operation_serde_roundtrip_connect() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let op = WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
};
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_serde_roundtrip_read() {
let oracle_id = OracleId::new(Pubkey::default(), 123u64);
let op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_target_oracle_websocket_serde_roundtrip() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let target = TargetOracle::WebSocket(WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
});
let json = serde_json::to_string(&target).expect("Failed to serialize");
let deserialized: TargetOracle =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(target, deserialized);
}
#[test]
fn test_websocket_operation_display() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let connect_op = WebSocketOperation::Connect {
url: "wss://example.com".into(),
oracle_id,
};
let display = format!("{}", connect_op);
assert!(display.contains("Connect"));
assert!(display.contains("wss://example.com"));
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let read_op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
let display = format!("{}", read_op);
assert!(display.contains("Read"));
assert!(display.contains("connection_oracle_id"));
}
#[test]
fn test_websocket_connect_with_encrypted_url() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let op = WebSocketOperation::Connect {
url: "enc://encrypted_websocket_url".into(),
oracle_id,
};
if let WebSocketOperation::Connect { url, .. } = &op {
assert_eq!(url.to_string(), "enc://encrypted_websocket_url");
}
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_oracle_info_with_websocket_target() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let mut info = base_valid_oracle_info();
info.target_oracles = vec![TargetOracle::WebSocket(WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
})];
assert!(info.validate().is_ok());
}
#[test]
fn test_websocket_operation_borsh_roundtrip_connect() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let op = WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
};
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_borsh_roundtrip_read() {
let oracle_id = OracleId::new(Pubkey::default(), 123u64);
let op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_borsh_roundtrip_encrypted_url() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let op = WebSocketOperation::Connect {
url: "enc://encrypted_websocket_url".into(),
oracle_id,
};
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_url_getter() {
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let connect_op = WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
};
assert!(connect_op.url().is_some());
assert_eq!(
connect_op.url().unwrap().to_string(),
"wss://example.com/stream"
);
let oracle_id = OracleId::new(Pubkey::default(), 42u64);
let read_op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
assert!(read_op.url().is_none());
}
#[test]
fn test_websocket_operation_connection_oracle_id_getter() {
let oracle_id = OracleId::new(Pubkey::default(), 42u64);
let read_op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
assert!(read_op.connection_oracle_id().is_some());
assert_eq!(*read_op.connection_oracle_id().unwrap(), oracle_id);
let oracle_id = OracleId::new(Pubkey::default(), 1u64);
let connect_op = WebSocketOperation::Connect {
url: "wss://example.com/stream".into(),
oracle_id,
};
assert!(connect_op.connection_oracle_id().is_none());
let oracle_id = OracleId::new(Pubkey::default(), 99u64);
let send_op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages: vec![],
};
assert!(send_op.connection_oracle_id().is_some());
assert_eq!(*send_op.connection_oracle_id().unwrap(), oracle_id);
}
#[test]
fn test_websocket_send_operation_creation() {
let oracle_id = OracleId::new(Pubkey::default(), 5u64);
let messages = vec![
OracleValue::Plain("message1".to_string()),
OracleValue::Plain("message2".to_string()),
];
let op = WebSocketOperation::send(oracle_id, messages.clone());
assert!(op.is_send());
assert!(!op.is_connect());
assert!(!op.is_read());
if let WebSocketOperation::Send {
connection_oracle_id,
messages: op_messages,
} = op
{
assert_eq!(connection_oracle_id, oracle_id);
assert_eq!(op_messages, messages);
} else {
panic!("Expected Send variant");
}
}
#[test]
fn test_websocket_send_messages_getter() {
let oracle_id = OracleId::new(Pubkey::default(), 10u64);
let messages = vec![OracleValue::Plain("test".to_string())];
let send_op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages: messages.clone(),
};
assert!(send_op.messages().is_some());
assert_eq!(send_op.messages().unwrap(), &messages[..]);
let connect_op = WebSocketOperation::Connect {
url: "wss://example.com".into(),
oracle_id,
};
assert!(connect_op.messages().is_none());
let read_op = WebSocketOperation::Read {
connection_oracle_id: oracle_id,
mode: WebSocketReadMode::default(),
};
assert!(read_op.messages().is_none());
}
#[test]
fn test_websocket_operation_serde_roundtrip_send() {
let oracle_id = OracleId::new(Pubkey::default(), 7u64);
let messages = vec![
OracleValue::Plain("plain_message".to_string()),
OracleValue::Encrypted("encrypted_data".to_string()),
];
let op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages,
};
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_borsh_roundtrip_send() {
let oracle_id = OracleId::new(Pubkey::default(), 8u64);
let messages = vec![
OracleValue::Plain("hello".to_string()),
OracleValue::Plain("world".to_string()),
];
let op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages,
};
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_send_display() {
let oracle_id = OracleId::new(Pubkey::default(), 20u64);
let messages = vec![
OracleValue::Plain("msg1".to_string()),
OracleValue::Plain("msg2".to_string()),
OracleValue::Plain("msg3".to_string()),
];
let send_op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages,
};
let display = format!("{}", send_op);
assert!(display.contains("Send"));
assert!(display.contains("connection_oracle_id"));
assert!(display.contains("messages_count=3"));
}
#[test]
fn test_websocket_send_empty_messages() {
let oracle_id = OracleId::new(Pubkey::default(), 15u64);
let op = WebSocketOperation::send(oracle_id, vec![]);
assert!(op.is_send());
assert_eq!(op.messages().unwrap().len(), 0);
let display = format!("{}", op);
assert!(display.contains("messages_count=0"));
}
#[test]
fn test_websocket_send_with_encrypted_messages() {
let oracle_id = OracleId::new(Pubkey::default(), 25u64);
let messages = vec![
OracleValue::Encrypted("encrypted1".to_string()),
OracleValue::Encrypted("encrypted2".to_string()),
];
let op = WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages: messages.clone(),
};
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_target_oracle_websocket_send_serde_roundtrip() {
let oracle_id = OracleId::new(Pubkey::default(), 30u64);
let messages = vec![OracleValue::Plain("test_message".to_string())];
let target = TargetOracle::WebSocket(WebSocketOperation::Send {
connection_oracle_id: oracle_id,
messages,
});
let json = serde_json::to_string(&target).expect("Failed to serialize");
let deserialized: TargetOracle =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(target, deserialized);
}
#[test]
fn test_websocket_close_operation_creation() {
let oracle_id = OracleId::new(Pubkey::default(), 50u64);
let op = WebSocketOperation::close(oracle_id);
assert!(op.is_close());
assert!(!op.is_connect());
assert!(!op.is_read());
assert!(!op.is_send());
if let WebSocketOperation::Close {
connection_oracle_id,
} = op
{
assert_eq!(connection_oracle_id, oracle_id);
} else {
panic!("Expected Close variant");
}
}
#[test]
fn test_websocket_close_connection_oracle_id_getter() {
let oracle_id = OracleId::new(Pubkey::default(), 55u64);
let close_op = WebSocketOperation::Close {
connection_oracle_id: oracle_id,
};
assert!(close_op.connection_oracle_id().is_some());
assert_eq!(*close_op.connection_oracle_id().unwrap(), oracle_id);
}
#[test]
fn test_websocket_operation_serde_roundtrip_close() {
let oracle_id = OracleId::new(Pubkey::default(), 60u64);
let op = WebSocketOperation::Close {
connection_oracle_id: oracle_id,
};
let json = serde_json::to_string(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_operation_borsh_roundtrip_close() {
let oracle_id = OracleId::new(Pubkey::default(), 65u64);
let op = WebSocketOperation::Close {
connection_oracle_id: oracle_id,
};
let bytes = borsh::to_vec(&op).expect("Failed to serialize");
let deserialized: WebSocketOperation =
borsh::from_slice(&bytes).expect("Failed to deserialize");
assert_eq!(op, deserialized);
}
#[test]
fn test_websocket_close_display() {
let oracle_id = OracleId::new(Pubkey::default(), 70u64);
let close_op = WebSocketOperation::Close {
connection_oracle_id: oracle_id,
};
let display = format!("{}", close_op);
assert!(display.contains("Close"));
assert!(display.contains("connection_oracle_id"));
assert!(display.contains(&oracle_id.to_string()));
}
#[test]
fn test_target_oracle_websocket_close_serde_roundtrip() {
let oracle_id = OracleId::new(Pubkey::default(), 75u64);
let target = TargetOracle::WebSocket(WebSocketOperation::Close {
connection_oracle_id: oracle_id,
});
let json = serde_json::to_string(&target).expect("Failed to serialize");
let deserialized: TargetOracle =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(target, deserialized);
}
}