Skip to content

Commit 71ea33d

Browse files
authored
fix(dpp): desiarilization of data contract JSON with token configuration (#2857)
1 parent 912ac2a commit 71ea33d

File tree

3 files changed

+379
-1
lines changed
  • packages/rs-dpp/src/data_contract
    • associated_token
      • token_perpetual_distribution/distribution_function
      • token_pre_programmed_distribution/v0
    • conversion/json

3 files changed

+379
-1
lines changed

packages/rs-dpp/src/data_contract/associated_token/token_perpetual_distribution/distribution_function/mod.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ pub enum DistributionFunction {
159159
///
160160
/// # Example
161161
/// - Emit 100 tokens per block for the first 1,000 blocks, then 50 tokens per block thereafter.
162-
Stepwise(BTreeMap<u64, TokenAmount>),
162+
Stepwise(
163+
#[serde(deserialize_with = "deserialize_u64_token_amount_map")] BTreeMap<u64, TokenAmount>,
164+
),
163165

164166
/// Emits tokens following a linear function that can increase or decrease over time
165167
/// with fractional precision.
@@ -558,6 +560,45 @@ pub enum DistributionFunction {
558560
},
559561
}
560562

563+
// Custom deserializer helper that accepts both key shapes for JSON and other serde formats:
564+
// - BTreeMap<u64, TokenAmount> // numeric timestamp/step keys
565+
// - BTreeMap<String, TokenAmount> // numeric-looking strings as keys
566+
// The function normalizes both into `BTreeMap<u64, TokenAmount>`. If a string key
567+
// cannot be parsed as `u64`, deserialization fails with a serde error.
568+
use serde::de::Deserializer;
569+
fn deserialize_u64_token_amount_map<'de, D>(
570+
deserializer: D,
571+
) -> Result<BTreeMap<u64, TokenAmount>, D::Error>
572+
where
573+
D: Deserializer<'de>,
574+
{
575+
// Untagged enum tries variants in order: attempt numeric keys first,
576+
// then fallback to string keys.
577+
#[derive(Deserialize)]
578+
#[serde(untagged)]
579+
enum U64OrStrMap<V> {
580+
// JSON: { 0: 100, 10: 50 }
581+
U64(BTreeMap<u64, V>),
582+
// JSON: { "0": 100, "10": 50 }
583+
Str(BTreeMap<String, V>),
584+
}
585+
586+
let helper: U64OrStrMap<TokenAmount> = U64OrStrMap::deserialize(deserializer)?;
587+
match helper {
588+
// Already numeric keys; return as-is
589+
U64OrStrMap::U64(m) => Ok(m),
590+
// Parse numeric-looking string keys into u64, preserving values
591+
U64OrStrMap::Str(sm) => sm
592+
.into_iter()
593+
.map(|(k, v)| {
594+
k.parse::<u64>()
595+
.map_err(serde::de::Error::custom)
596+
.map(|kk| (kk, v))
597+
})
598+
.collect(),
599+
}
600+
}
601+
561602
impl fmt::Display for DistributionFunction {
562603
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563604
match self {

packages/rs-dpp/src/data_contract/associated_token/token_pre_programmed_distribution/v0/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::fmt;
1010
#[derive(Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq)]
1111
#[serde(rename_all = "camelCase")]
1212
pub struct TokenPreProgrammedDistributionV0 {
13+
#[serde(deserialize_with = "deserialize_ts_to_id_amount_map")]
1314
pub distributions: BTreeMap<TimestampMillis, BTreeMap<Identifier, TokenAmount>>,
1415
}
1516

@@ -26,3 +27,46 @@ impl fmt::Display for TokenPreProgrammedDistributionV0 {
2627
write!(f, "}}")
2728
}
2829
}
30+
31+
use serde::de::Deserializer;
32+
33+
// Custom deserializer for `distributions` that tolerates two JSON shapes:
34+
// - a map keyed by timestamp millis as numbers (u64)
35+
// - a map keyed by timestamp millis as strings (e.g. "1735689600000")
36+
// It normalizes both into `BTreeMap<u64, V>` where V is the inner value map
37+
// (`BTreeMap<Identifier, TokenAmount>` here). If a string key cannot be parsed
38+
// as `u64`, deserialization fails with a serde error. Using `BTreeMap` keeps
39+
// keys ordered by timestamp.
40+
fn deserialize_ts_to_id_amount_map<'de, D>(
41+
deserializer: D,
42+
) -> Result<BTreeMap<TimestampMillis, BTreeMap<Identifier, TokenAmount>>, D::Error>
43+
where
44+
D: Deserializer<'de>,
45+
{
46+
// Untagged enum attempts the variants in order: first try a map with u64
47+
// keys; if that doesn't match, try a map with string keys.
48+
#[derive(Deserialize)]
49+
#[serde(untagged)]
50+
enum U64OrStrTs<V> {
51+
// JSON: { 1735689600000: { <id>: <amount>, ... }, ... }
52+
U64(BTreeMap<TimestampMillis, V>),
53+
// JSON: { "1735689600000": { <id>: <amount>, ... }, ... }
54+
Str(BTreeMap<String, V>),
55+
}
56+
57+
let helper: U64OrStrTs<BTreeMap<Identifier, TokenAmount>> =
58+
U64OrStrTs::deserialize(deserializer)?;
59+
match helper {
60+
// Already has numeric timestamp keys; return as-is
61+
U64OrStrTs::U64(m) => Ok(m),
62+
// Convert string timestamp keys into u64, preserving values unchanged
63+
U64OrStrTs::Str(sm) => sm
64+
.into_iter()
65+
.map(|(k, v)| {
66+
k.parse::<u64>()
67+
.map_err(serde::de::Error::custom)
68+
.map(|ts| (ts, v))
69+
})
70+
.collect(),
71+
}
72+
}

0 commit comments

Comments
 (0)