diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 71767e6a..c99cd8bc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3565,6 +3565,7 @@ dependencies = [ "async-trait", "hex", "rusoto_core", + "serde", "starknet 0.11.0", "strum", "strum_macros", diff --git a/rust/pragma-utils/Cargo.toml b/rust/pragma-utils/Cargo.toml index 424f2a01..c18bce0a 100644 --- a/rust/pragma-utils/Cargo.toml +++ b/rust/pragma-utils/Cargo.toml @@ -16,3 +16,4 @@ rusoto_core = { workspace = true } hex = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } +serde = { workspace = true, features = ["derive"] } diff --git a/rust/pragma-utils/src/feeds/mod.rs b/rust/pragma-utils/src/feeds/mod.rs index 3615639d..0e9dcbbe 100644 --- a/rust/pragma-utils/src/feeds/mod.rs +++ b/rust/pragma-utils/src/feeds/mod.rs @@ -39,16 +39,18 @@ use std::str::FromStr; use anyhow::{anyhow, bail, Context}; use hex; +use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Feed { + pub feed_id: String, pub asset_class: AssetClass, pub feed_type: FeedType, pub pair_id: String, } -#[derive(Debug, PartialEq, Display, EnumString)] +#[derive(Debug, PartialEq, Display, EnumString, Serialize, Deserialize)] pub enum AssetClass { Crypto = 1, } @@ -64,7 +66,7 @@ impl TryFrom for AssetClass { } } -#[derive(Debug, PartialEq, Display, EnumString)] +#[derive(Debug, PartialEq, Display, EnumString, Serialize, Deserialize)] pub enum FeedType { #[strum(serialize = "Spot Median")] SpotMedian = 21325, @@ -94,8 +96,8 @@ impl FromStr for Feed { type Err = anyhow::Error; fn from_str(feed_id: &str) -> anyhow::Result { - let feed_id = feed_id.strip_prefix("0x").unwrap_or(feed_id); - let mut bytes = hex::decode(feed_id)?; + let stripped_id = feed_id.strip_prefix("0x").unwrap_or(feed_id); + let mut bytes = hex::decode(stripped_id)?; if bytes.len() < 3 { bail!("Feed ID is too short"); @@ -120,7 +122,7 @@ impl FromStr for Feed { bail!("Empty pair ID"); } - Ok(Feed { asset_class, feed_type, pair_id }) + Ok(Feed { feed_id: feed_id.to_owned(), asset_class, feed_type, pair_id }) } } diff --git a/rust/theoros/openapi.json b/rust/theoros/openapi.json index c557c844..1c4510a0 100644 --- a/rust/theoros/openapi.json +++ b/rust/theoros/openapi.json @@ -9,22 +9,22 @@ "version": "0.1.0" }, "paths": { - "/v1/calldata": { + "/v1/calldata/{feed_id}": { "get": { "tags": [ - "crate::handlers::get_data_feeds" + "crate::handlers::get_calldata" ], - "operationId": "get_data_feeds", + "operationId": "get_calldata", "parameters": [], "responses": { "200": { - "description": "Get all the available data feeds", + "description": "Constructs the calldata used to update the feed id specified", "content": { "application/json": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GetDataFeedsResponse" + "$ref": "#/components/schemas/GetCalldataResponse" } } } @@ -33,22 +33,21 @@ } } }, - "/v1/calldata/{data_feed_id}": { + "/v1/data_feeds": { "get": { "tags": [ - "crate::handlers::get_calldata" + "crate::handlers::get_data_feeds" ], - "operationId": "get_calldata", - "parameters": [], + "operationId": "get_data_feeds", "responses": { "200": { - "description": "Constructs the calldata used to update the data feed id specified", + "description": "Get all the available feed ids", "content": { "application/json": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/GetCalldataResponse" + "$ref": "#/components/schemas/GetDataFeedsResponse" } } } @@ -82,19 +81,30 @@ } }, "GetDataFeedsError": { - "type": "string", - "enum": [ - "InternalServerError", - "DatabaseConnection" + "oneOf": [ + { + "type": "object", + "required": [ + "ParsingFeedId" + ], + "properties": { + "ParsingFeedId": { + "type": "string" + } + } + }, + { + "type": "string", + "enum": [ + "InternalServerError" + ] + } ] }, - "GetDataFeedsQuery": { - "type": "object" - }, "GetDataFeedsResponse": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Feed" } } }, @@ -124,7 +134,7 @@ "schema": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Feed" } } } @@ -135,7 +145,7 @@ "tags": [ { "name": "theoros", - "description": "Theoros - the Pragma Consultant" + "description": "Theoros - The Pragma Consultant" } ] } \ No newline at end of file diff --git a/rust/theoros/src/errors/data_feeds_error.rs b/rust/theoros/src/errors/data_feeds_error.rs index cef60486..c0b73f43 100644 --- a/rust/theoros/src/errors/data_feeds_error.rs +++ b/rust/theoros/src/errors/data_feeds_error.rs @@ -5,18 +5,16 @@ use utoipa::ToSchema; #[derive(Debug, thiserror::Error, ToSchema)] #[allow(unused)] pub enum GetDataFeedsError { + #[error("could not parse feed id: {0}")] + ParsingFeedId(String), #[error("internal server error")] InternalServerError, - #[error("could not establish a connection with the database")] - DatabaseConnection, } impl IntoResponse for GetDataFeedsError { fn into_response(self) -> axum::response::Response { let (status, err_msg) = match self { - Self::DatabaseConnection => { - (StatusCode::SERVICE_UNAVAILABLE, "Could not establish a connection with the Database".to_string()) - } + Self::ParsingFeedId(feed_id) => (StatusCode::PROCESSING, format!("Could not parse feed: {feed_id}")), _ => (StatusCode::INTERNAL_SERVER_ERROR, String::from("Internal server error")), }; (status, Json(json!({"resource":"Calldata", "message": err_msg, "happened_at" : chrono::Utc::now() }))) diff --git a/rust/theoros/src/handlers/get_calldata.rs b/rust/theoros/src/handlers/get_calldata.rs index da613a41..db9d81d6 100644 --- a/rust/theoros/src/handlers/get_calldata.rs +++ b/rust/theoros/src/handlers/get_calldata.rs @@ -17,11 +17,11 @@ pub struct GetCalldataResponse { #[utoipa::path( get, - path = "/v1/calldata/{data_feed_id}", + path = "/v1/calldata/{feed_id}", responses( ( status = 200, - description = "Constructs the calldata used to update the data feed id specified", + description = "Constructs the calldata used to update the feed id specified", body = [GetCalldataResponse] ) ), @@ -31,9 +31,9 @@ pub struct GetCalldataResponse { )] pub async fn get_calldata( State(_state): State, - PathExtractor(data_feed_id): PathExtractor, + PathExtractor(feed_id): PathExtractor, Query(_params): Query, ) -> Result, GetCalldataError> { - tracing::info!("Received get calldata request for feed: {data_feed_id}"); + tracing::info!("Received get calldata request for feed: {feed_id}"); Ok(Json(GetCalldataResponse::default())) } diff --git a/rust/theoros/src/handlers/get_data_feeds.rs b/rust/theoros/src/handlers/get_data_feeds.rs index a24b85f1..dd403c9f 100644 --- a/rust/theoros/src/handlers/get_data_feeds.rs +++ b/rust/theoros/src/handlers/get_data_feeds.rs @@ -1,5 +1,6 @@ use axum::extract::State; use axum::Json; +use pragma_utils::feeds::Feed; use serde::{Deserialize, Serialize}; use utoipa::{ToResponse, ToSchema}; @@ -7,27 +8,26 @@ use crate::errors::GetDataFeedsError; use crate::AppState; #[derive(Debug, Default, Serialize, Deserialize, ToResponse, ToSchema)] -pub struct FeedId { - r#id: String, - name: String, -} - -#[derive(Debug, Default, Serialize, Deserialize, ToResponse, ToSchema)] -pub struct GetDataFeedsResponse(pub Vec); +pub struct GetDataFeedsResponse(pub Vec); #[utoipa::path( get, path = "/v1/data_feeds", responses( - (status = 200, description = "Get all the available data feeds", body = [GetDataFeedsResponse]) + (status = 200, description = "Get all the available feed ids", body = [GetDataFeedsResponse]) ), )] pub async fn get_data_feeds(State(state): State) -> Result, GetDataFeedsError> { - tracing::info!("Received get calldata request"); + tracing::info!("Received get all data feeds request"); + + let stored_feed_ids = state.storage.data_feeds(); - let _available_data_feeds = state.storage.data_feeds(); + let mut feeds = Vec::with_capacity(stored_feed_ids.len()); + for feed_id in stored_feed_ids { + let feed = feed_id.parse().map_err(|_| GetDataFeedsError::ParsingFeedId(feed_id.clone()))?; + feeds.push(feed); + } - // TODO: feeds - let response = GetDataFeedsResponse(vec![]); + let response = GetDataFeedsResponse(feeds); Ok(Json(response)) } diff --git a/rust/theoros/src/main.rs b/rust/theoros/src/main.rs index a0397be6..38e4a0ee 100644 --- a/rust/theoros/src/main.rs +++ b/rust/theoros/src/main.rs @@ -35,7 +35,7 @@ const APIBARA_DNA_URL: &str = "https://sepolia.starknet.a5a.ch"; // TODO: Should const SERVER_HOST: &str = "0.0.0.0"; const SERVER_PORT: u16 = 3000; -const PRAGMA_WRAPPER_CONTRACT_ADDRESS: Felt = Felt::ZERO; +const _PRAGMA_WRAPPER_CONTRACT_ADDRESS: Felt = Felt::ZERO; const HYPERLANE_CORE_CONTRACT_ADDRESS: Felt = Felt::ZERO; #[derive(Clone)] @@ -55,9 +55,11 @@ async fn main() -> Result<()> { let rpc_client = StarknetRpc::new(rpc_url); // New storage + initialization - let theoros_storage = - TheorosStorage::from_rpc_state(&rpc_client, &PRAGMA_WRAPPER_CONTRACT_ADDRESS, &HYPERLANE_CORE_CONTRACT_ADDRESS) - .await?; + // let theoros_storage = + // TheorosStorage::from_rpc_state(&rpc_client, &PRAGMA_WRAPPER_CONTRACT_ADDRESS, &HYPERLANE_CORE_CONTRACT_ADDRESS) + // .await?; + // TODO: remove & uncomment line above when we can fetch from pragma wrapper + hyperlane + let theoros_storage = TheorosStorage::testing_state(); // Theoros metrics let metrics_service = MetricsService::new(false, METRICS_PORT)?; diff --git a/rust/theoros/src/services/api/docs.rs b/rust/theoros/src/services/api/docs.rs index 1222f590..8a0c0786 100644 --- a/rust/theoros/src/services/api/docs.rs +++ b/rust/theoros/src/services/api/docs.rs @@ -5,7 +5,7 @@ use serde_json::to_string_pretty; use utoipa::OpenApi; use utoipauto::utoipauto; -#[utoipauto(paths = "./theoros/src")] +#[utoipauto(paths = "./theoros/src, ./pragma-utils/src")] #[derive(OpenApi)] #[openapi( tags( diff --git a/rust/theoros/src/services/api/router.rs b/rust/theoros/src/services/api/router.rs index 99e021d3..2cef1780 100644 --- a/rust/theoros/src/services/api/router.rs +++ b/rust/theoros/src/services/api/router.rs @@ -29,7 +29,7 @@ async fn handler_404() -> impl IntoResponse { } fn calldata_routes(state: AppState) -> Router { - Router::new().route("/calldata", get(get_calldata)).with_state(state) + Router::new().route("/calldata/:feed_id", get(get_calldata)).with_state(state) } fn data_feeds_routes(state: AppState) -> Router { diff --git a/rust/theoros/src/storage/mod.rs b/rust/theoros/src/storage/mod.rs index d2234693..035007f2 100644 --- a/rust/theoros/src/storage/mod.rs +++ b/rust/theoros/src/storage/mod.rs @@ -25,6 +25,16 @@ pub struct TheorosStorage { } impl TheorosStorage { + // TODO: remove this later, only used for now for tests + pub fn testing_state() -> Self { + let constants_data_feeds: HashSet = HashSet::from([ + "0x01534d4254432f555344".into(), // BTC/USD + "0x01534d4554482f555344".into(), // ETH/USD + "0x01534d454b55424f2f555344".into(), // EKUBO/USD + ]); + Self { data_feeds: constants_data_feeds, ..Default::default() } + } + pub async fn from_rpc_state( rpc_client: &StarknetRpc, pragma_wrapper_address: &Felt,