Ver Fonte

feat(pyth-lazer) Create schema for lazer agent JRPC endpoint (#2836)

Bart Platak há 4 meses atrás
pai
commit
01880f798f

+ 11 - 11
Cargo.lock

@@ -5540,8 +5540,8 @@ dependencies = [
  "hyper 1.6.0",
  "hyper-util",
  "protobuf",
- "pyth-lazer-protocol 0.7.2",
- "pyth-lazer-publisher-sdk 0.1.5",
+ "pyth-lazer-protocol 0.7.3",
+ "pyth-lazer-publisher-sdk 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde",
  "serde_json",
  "soketto",
@@ -5569,7 +5569,7 @@ dependencies = [
  "futures-util",
  "hex",
  "libsecp256k1 0.7.2",
- "pyth-lazer-protocol 0.7.3",
+ "pyth-lazer-protocol 0.8.0",
  "serde",
  "serde_json",
  "tokio",
@@ -5580,9 +5580,9 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.7.2"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9bdf4e2ba853a8b437309487542e742c7d094d8db189db194cb538f2be02ecd"
+checksum = "6445dc5d2f7fff7c677fb8edc5a080a82ef7583c1bdb39daa95421788c23f695"
 dependencies = [
  "anyhow",
  "base64 0.22.1",
@@ -5597,17 +5597,17 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.7.3"
+version = "0.8.0"
 dependencies = [
  "alloy-primitives 0.8.25",
  "anyhow",
- "base64 0.22.1",
  "bincode 1.3.3",
  "bs58",
  "byteorder",
  "derive_more 1.0.0",
  "ed25519-dalek 2.1.1",
  "hex",
+ "humantime-serde",
  "itertools 0.13.0",
  "libsecp256k1 0.7.2",
  "protobuf",
@@ -5618,16 +5618,14 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-publisher-sdk"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e633db28ca38210de8ab3e99d5bd85ad8cae08a08bb0292506340ee9d1c718"
+version = "0.1.6"
 dependencies = [
  "anyhow",
  "fs-err",
  "humantime",
  "protobuf",
  "protobuf-codegen",
- "pyth-lazer-protocol 0.7.2",
+ "pyth-lazer-protocol 0.8.0",
  "serde-value",
  "tracing",
 ]
@@ -5635,6 +5633,8 @@ dependencies = [
 [[package]]
 name = "pyth-lazer-publisher-sdk"
 version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6ef4052ebf2a7943259b3d52a10b2231ffc346717735c50e44d73fe92019d5"
 dependencies = [
  "anyhow",
  "fs-err",

+ 13 - 23
lazer/contracts/solana/Cargo.lock

@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "Inflector"
@@ -616,12 +616,6 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
-[[package]]
-name = "base64"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
-
 [[package]]
 name = "base64ct"
 version = "1.6.0"
@@ -2006,6 +2000,16 @@ version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
 
+[[package]]
+name = "humantime-serde"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
+dependencies = [
+ "humantime",
+ "serde",
+]
+
 [[package]]
 name = "hyper"
 version = "0.14.31"
@@ -3200,12 +3204,12 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.7.3"
+version = "0.8.0"
 dependencies = [
  "anyhow",
- "base64 0.22.1",
  "byteorder",
  "derive_more",
+ "humantime-serde",
  "itertools 0.13.0",
  "protobuf",
  "rust_decimal",
@@ -3213,20 +3217,6 @@ dependencies = [
  "serde_json",
 ]
 
-[[package]]
-name = "pyth-lazer-publisher-sdk"
-version = "0.1.6"
-dependencies = [
- "anyhow",
- "fs-err",
- "humantime",
- "protobuf",
- "protobuf-codegen",
- "pyth-lazer-protocol",
- "serde-value",
- "tracing",
-]
-
 [[package]]
 name = "pyth-lazer-solana-contract"
 version = "0.4.2"

+ 1 - 1
lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml

@@ -19,7 +19,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
-pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.7.2" }
+pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.8.0" }
 
 anchor-lang = "0.30.1"
 bytemuck = "1.20.0"

+ 1 - 1
lazer/publisher_sdk/rust/Cargo.toml

@@ -7,7 +7,7 @@ license = "Apache-2.0"
 repository = "https://github.com/pyth-network/pyth-crosschain"
 
 [dependencies]
-pyth-lazer-protocol = { version = "0.7.2", path = "../../sdk/rust/protocol" }
+pyth-lazer-protocol = { version = "0.8.0", path = "../../sdk/rust/protocol" }
 anyhow = "1.0.98"
 protobuf = "3.7.2"
 serde-value = "0.7.0"

+ 38 - 0
lazer/publisher_sdk/rust/src/lib.rs

@@ -1,9 +1,12 @@
 use std::{collections::BTreeMap, time::Duration};
 
+use crate::publisher_update::feed_update::Update;
+use crate::publisher_update::{FeedUpdate, FundingRateUpdate, PriceUpdate};
 use ::protobuf::MessageField;
 use anyhow::{bail, ensure, Context};
 use humantime::format_duration;
 use protobuf::dynamic_value::{dynamic_value, DynamicValue};
+use pyth_lazer_protocol::jrpc::{FeedUpdateParams, UpdateParams};
 use pyth_lazer_protocol::router::TimestampUs;
 
 pub mod transaction_envelope {
@@ -164,3 +167,38 @@ impl TryFrom<DynamicValue> for serde_value::Value {
         }
     }
 }
+
+impl From<FeedUpdateParams> for FeedUpdate {
+    fn from(value: FeedUpdateParams) -> Self {
+        FeedUpdate {
+            feed_id: Some(value.feed_id.0),
+            source_timestamp: value.source_timestamp.into(),
+            update: Some(value.update.into()),
+            special_fields: Default::default(),
+        }
+    }
+}
+
+impl From<UpdateParams> for Update {
+    fn from(value: UpdateParams) -> Self {
+        match value {
+            UpdateParams::PriceUpdate {
+                price,
+                best_bid_price,
+                best_ask_price,
+            } => Update::PriceUpdate(PriceUpdate {
+                price: Some(price.0.into()),
+                best_bid_price: best_bid_price.map(|p| p.0.into()),
+                best_ask_price: best_ask_price.map(|p| p.0.into()),
+                special_fields: Default::default(),
+            }),
+            UpdateParams::FundingRateUpdate { price, rate } => {
+                Update::FundingRateUpdate(FundingRateUpdate {
+                    price: price.map(|p| p.0.into()),
+                    rate: Some(rate.0),
+                    special_fields: Default::default(),
+                })
+            }
+        }
+    }
+}

+ 1 - 1
lazer/sdk/rust/client/Cargo.toml

@@ -6,7 +6,7 @@ description = "A Rust client for Pyth Lazer"
 license = "Apache-2.0"
 
 [dependencies]
-pyth-lazer-protocol = { path = "../protocol", version = "0.7.2" }
+pyth-lazer-protocol = { path = "../protocol", version = "0.8.0" }
 tokio = { version = "1", features = ["full"] }
 tokio-tungstenite = { version = "0.20", features = ["native-tls"] }
 futures-util = "0.3"

+ 2 - 2
lazer/sdk/rust/protocol/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "pyth-lazer-protocol"
-version = "0.7.3"
+version = "0.8.0"
 edition = "2021"
 description = "Pyth Lazer SDK - protocol types."
 license = "Apache-2.0"
@@ -14,8 +14,8 @@ serde_json = "1.0"
 derive_more = { version = "1.0.0", features = ["from"] }
 itertools = "0.13.0"
 rust_decimal = "1.36.0"
-base64 = "0.22.1"
 protobuf = "3.7.2"
+humantime-serde = "1.1.1"
 
 [dev-dependencies]
 bincode = "1.3.3"

+ 398 - 0
lazer/sdk/rust/protocol/src/jrpc.rs

@@ -0,0 +1,398 @@
+use crate::router::{Channel, Price, PriceFeedId, Rate, TimestampUs};
+use crate::symbol_state::SymbolState;
+use serde::{Deserialize, Serialize};
+use std::time::Duration;
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct PythLazerAgentJrpcV1 {
+    pub jsonrpc: JsonRpcVersion,
+    #[serde(flatten)]
+    pub params: JrpcCall,
+    pub id: i64,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[serde(tag = "method", content = "params")]
+#[serde(rename_all = "snake_case")]
+pub enum JrpcCall {
+    PushUpdate(FeedUpdateParams),
+    GetMetadata(GetMetadataParams),
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct FeedUpdateParams {
+    pub feed_id: PriceFeedId,
+    pub source_timestamp: TimestampUs,
+    pub update: UpdateParams,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[serde(tag = "type")]
+pub enum UpdateParams {
+    #[serde(rename = "price")]
+    PriceUpdate {
+        price: Price,
+        best_bid_price: Option<Price>,
+        best_ask_price: Option<Price>,
+    },
+    #[serde(rename = "funding_rate")]
+    FundingRateUpdate { price: Option<Price>, rate: Rate },
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct Filter {
+    pub name: Option<String>,
+    pub asset_type: Option<String>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct GetMetadataParams {
+    pub names: Option<Vec<String>>,
+    pub asset_types: Option<Vec<String>>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub enum JsonRpcVersion {
+    #[serde(rename = "2.0")]
+    V2,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub enum JrpcResponse<T> {
+    Success(JrpcSuccessResponse<T>),
+    Error(JrpcErrorResponse),
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct JrpcSuccessResponse<T> {
+    pub jsonrpc: JsonRpcVersion,
+    pub result: T,
+    pub id: i64,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct JrpcErrorResponse {
+    pub jsonrpc: JsonRpcVersion,
+    pub error: JrpcErrorObject,
+    pub id: Option<i64>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct JrpcErrorObject {
+    pub code: i64,
+    pub message: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub data: Option<serde_json::Value>,
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum JrpcError {
+    ParseError,
+    InternalError,
+}
+
+// note: error codes can be found in the rfc https://www.jsonrpc.org/specification#error_object
+impl From<JrpcError> for JrpcErrorObject {
+    fn from(error: JrpcError) -> Self {
+        match error {
+            JrpcError::ParseError => JrpcErrorObject {
+                code: -32700,
+                message: "Parse error".to_string(),
+                data: None,
+            },
+            JrpcError::InternalError => JrpcErrorObject {
+                code: -32603,
+                message: "Internal error".to_string(),
+                data: None,
+            },
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct SymbolMetadata {
+    pub pyth_lazer_id: PriceFeedId,
+    pub name: String,
+    pub symbol: String,
+    pub description: String,
+    pub asset_type: String,
+    pub exponent: i16,
+    pub cmc_id: Option<u32>,
+    #[serde(default, with = "humantime_serde", alias = "interval")]
+    pub funding_rate_interval: Option<Duration>,
+    pub min_publishers: u16,
+    pub min_channel: Channel,
+    pub state: SymbolState,
+    pub hermes_id: Option<String>,
+    pub quote_currency: Option<String>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::jrpc::JrpcCall::{GetMetadata, PushUpdate};
+
+    #[test]
+    fn test_push_update_price() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "push_update",
+          "params": {
+            "feed_id": 1,
+            "source_timestamp": 124214124124,
+
+            "update": {
+              "type": "price",
+              "price": 1234567890,
+              "best_bid_price": 1234567891,
+              "best_ask_price": 1234567892
+            }
+          },
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: PushUpdate(FeedUpdateParams {
+                feed_id: PriceFeedId(1),
+                source_timestamp: TimestampUs(124214124124),
+                update: UpdateParams::PriceUpdate {
+                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()),
+                    best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()),
+                },
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+
+    #[test]
+    fn test_push_update_price_without_bid_ask() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "push_update",
+          "params": {
+            "feed_id": 1,
+            "source_timestamp": 124214124124,
+
+            "update": {
+              "type": "price",
+              "price": 1234567890
+            }
+          },
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: PushUpdate(FeedUpdateParams {
+                feed_id: PriceFeedId(1),
+                source_timestamp: TimestampUs(124214124124),
+                update: UpdateParams::PriceUpdate {
+                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    best_bid_price: None,
+                    best_ask_price: None,
+                },
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+
+    #[test]
+    fn test_push_update_funding_rate() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "push_update",
+          "params": {
+            "feed_id": 1,
+            "source_timestamp": 124214124124,
+
+            "update": {
+              "type": "funding_rate",
+              "price": 1234567890,
+              "rate": 1234567891
+            }
+          },
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: PushUpdate(FeedUpdateParams {
+                feed_id: PriceFeedId(1),
+                source_timestamp: TimestampUs(124214124124),
+                update: UpdateParams::FundingRateUpdate {
+                    price: Some(Price::from_integer(1234567890, 0).unwrap()),
+                    rate: Rate::from_integer(1234567891, 0).unwrap(),
+                },
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+    #[test]
+    fn test_push_update_funding_rate_without_price() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "push_update",
+          "params": {
+            "feed_id": 1,
+            "source_timestamp": 124214124124,
+
+            "update": {
+              "type": "funding_rate",
+              "rate": 1234567891
+            }
+          },
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: PushUpdate(FeedUpdateParams {
+                feed_id: PriceFeedId(1),
+                source_timestamp: TimestampUs(124214124124),
+                update: UpdateParams::FundingRateUpdate {
+                    price: None,
+                    rate: Rate::from_integer(1234567891, 0).unwrap(),
+                },
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+
+    #[test]
+    fn test_send_get_metadata() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "get_metadata",
+          "params": {
+            "names": ["BTC/USD"],
+            "asset_types": ["crypto"]
+          },
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: GetMetadata(GetMetadataParams {
+                names: Some(vec!["BTC/USD".to_string()]),
+                asset_types: Some(vec!["crypto".to_string()]),
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+
+    #[test]
+    fn test_get_metadata_without_filters() {
+        let json = r#"
+        {
+          "jsonrpc": "2.0",
+          "method": "get_metadata",
+          "params": {},
+          "id": 1
+        }
+        "#;
+
+        let expected = PythLazerAgentJrpcV1 {
+            jsonrpc: JsonRpcVersion::V2,
+            params: GetMetadata(GetMetadataParams {
+                names: None,
+                asset_types: None,
+            }),
+            id: 1,
+        };
+
+        assert_eq!(
+            serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
+            expected
+        );
+    }
+
+    #[test]
+    fn test_response_format_error() {
+        let response = serde_json::from_str::<JrpcErrorResponse>(
+            r#"
+            {
+              "jsonrpc": "2.0",
+              "id": 2,
+              "error": {
+                "message": "Internal error",
+                "code": -32603
+              }
+            }
+            "#,
+        )
+        .unwrap();
+
+        assert_eq!(
+            response,
+            JrpcErrorResponse {
+                jsonrpc: JsonRpcVersion::V2,
+                error: JrpcErrorObject {
+                    code: -32603,
+                    message: "Internal error".to_string(),
+                    data: None,
+                },
+                id: Some(2),
+            }
+        );
+    }
+
+    #[test]
+    pub fn test_response_format_success() {
+        let response = serde_json::from_str::<JrpcSuccessResponse<String>>(
+            r#"
+            {
+              "jsonrpc": "2.0",
+              "id": 2,
+              "result": "success"
+            }
+            "#,
+        )
+        .unwrap();
+
+        assert_eq!(
+            response,
+            JrpcSuccessResponse::<String> {
+                jsonrpc: JsonRpcVersion::V2,
+                result: "success".to_string(),
+                id: 2,
+            }
+        );
+    }
+}

+ 3 - 2
lazer/sdk/rust/protocol/src/lib.rs

@@ -2,6 +2,7 @@
 
 pub mod api;
 pub mod binary_update;
+pub mod jrpc;
 pub mod message;
 pub mod payload;
 pub mod publisher;
@@ -23,7 +24,7 @@ fn magics_in_big_endian() {
     };
 
     // The values listed in this test can be used when reading the magic headers in BE format
-    // (e.g. on EVM).
+    // (e.g., on EVM).
 
     assert_eq!(u32::swap_bytes(BINARY_UPDATE_FORMAT_MAGIC), 1937213467);
     assert_eq!(u32::swap_bytes(PAYLOAD_FORMAT_MAGIC), 1976813459);
@@ -44,6 +45,6 @@ fn magics_in_big_endian() {
         LE_UNSIGNED_FORMAT_MAGIC,
     ] {
         // Required to distinguish between byte orders.
-        assert!(u32::swap_bytes(magic) != magic);
+        assert_ne!(u32::swap_bytes(magic), magic);
     }
 }

+ 30 - 7
lazer/sdk/rust/protocol/src/router.rs

@@ -1,5 +1,6 @@
-//! WebSocket JSON protocol types for API the router provides to consumers and publishers.
+//! WebSocket JSON protocol types for the API the router provides to consumers and publishers.
 
+use protobuf::MessageField;
 use {
     crate::payload::AggregatedPriceFeedData,
     anyhow::{bail, Context},
@@ -37,6 +38,26 @@ impl TryFrom<&Timestamp> for TimestampUs {
     }
 }
 
+impl From<TimestampUs> for Timestamp {
+    fn from(value: TimestampUs) -> Self {
+        Timestamp {
+            #[allow(
+                clippy::cast_possible_wrap,
+                reason = "u64 to i64 after this division can never overflow because the value cannot be too big"
+            )]
+            seconds: (value.0 / 1_000_000) as i64,
+            nanos: (value.0 % 1_000_000) as i32 * 1000,
+            special_fields: Default::default(),
+        }
+    }
+}
+
+impl From<TimestampUs> for MessageField<Timestamp> {
+    fn from(value: TimestampUs) -> Self {
+        MessageField::some(value.into())
+    }
+}
+
 impl TimestampUs {
     pub fn now() -> Self {
         let value = SystemTime::now()
@@ -304,7 +325,7 @@ impl<'de> Deserialize<'de> for Channel {
         D: serde::Deserializer<'de>,
     {
         let value = <String>::deserialize(deserializer)?;
-        parse_channel(&value).ok_or_else(|| D::Error::custom("unknown channel"))
+        parse_channel(&value).ok_or_else(|| Error::custom("unknown channel"))
     }
 }
 
@@ -341,12 +362,14 @@ fn fixed_rate_values() {
         "values must be unique and sorted"
     );
     for value in FixedRate::ALL {
-        assert!(
-            1000 % value.ms == 0,
+        assert_eq!(
+            1000 % value.ms,
+            0,
             "1 s must contain whole number of intervals"
         );
-        assert!(
-            value.value_us() % FixedRate::MIN.value_us() == 0,
+        assert_eq!(
+            value.value_us() % FixedRate::MIN.value_us(),
+            0,
             "the interval's borders must be a subset of the minimal interval's borders"
         );
     }
@@ -383,7 +406,7 @@ impl<'de> Deserialize<'de> for SubscriptionParams {
         D: serde::Deserializer<'de>,
     {
         let value = SubscriptionParamsRepr::deserialize(deserializer)?;
-        Self::new(value).map_err(D::Error::custom)
+        Self::new(value).map_err(Error::custom)
     }
 }