Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ pub enum DistributionFunction {
///
/// # Example
/// - Emit 100 tokens per block for the first 1,000 blocks, then 50 tokens per block thereafter.
Stepwise(BTreeMap<u64, TokenAmount>),
Stepwise(
#[serde(deserialize_with = "deserialize_u64_token_amount_map")] BTreeMap<u64, TokenAmount>,
),

/// Emits tokens following a linear function that can increase or decrease over time
/// with fractional precision.
Expand Down Expand Up @@ -558,6 +560,45 @@ pub enum DistributionFunction {
},
}

// Custom deserializer helper that accepts both key shapes for JSON and other serde formats:
// - BTreeMap<u64, TokenAmount> // numeric timestamp/step keys
// - BTreeMap<String, TokenAmount> // numeric-looking strings as keys
// The function normalizes both into `BTreeMap<u64, TokenAmount>`. If a string key
// cannot be parsed as `u64`, deserialization fails with a serde error.
use serde::de::Deserializer;
fn deserialize_u64_token_amount_map<'de, D>(
deserializer: D,
) -> Result<BTreeMap<u64, TokenAmount>, D::Error>
where
D: Deserializer<'de>,
{
// Untagged enum tries variants in order: attempt numeric keys first,
// then fallback to string keys.
#[derive(Deserialize)]
#[serde(untagged)]
enum U64OrStrMap<V> {
// JSON: { 0: 100, 10: 50 }
U64(BTreeMap<u64, V>),
// JSON: { "0": 100, "10": 50 }
Str(BTreeMap<String, V>),
}

let helper: U64OrStrMap<TokenAmount> = U64OrStrMap::deserialize(deserializer)?;
match helper {
// Already numeric keys; return as-is
U64OrStrMap::U64(m) => Ok(m),
// Parse numeric-looking string keys into u64, preserving values
U64OrStrMap::Str(sm) => sm
.into_iter()
.map(|(k, v)| {
k.parse::<u64>()
.map_err(serde::de::Error::custom)
.map(|kk| (kk, v))
})
.collect(),
}
}

impl fmt::Display for DistributionFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::fmt;
#[derive(Serialize, Deserialize, Decode, Encode, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TokenPreProgrammedDistributionV0 {
#[serde(deserialize_with = "deserialize_ts_to_id_amount_map")]
pub distributions: BTreeMap<TimestampMillis, BTreeMap<Identifier, TokenAmount>>,
}

Expand All @@ -26,3 +27,46 @@ impl fmt::Display for TokenPreProgrammedDistributionV0 {
write!(f, "}}")
}
}

use serde::de::Deserializer;

// Custom deserializer for `distributions` that tolerates two JSON shapes:
// - a map keyed by timestamp millis as numbers (u64)
// - a map keyed by timestamp millis as strings (e.g. "1735689600000")
// It normalizes both into `BTreeMap<u64, V>` where V is the inner value map
// (`BTreeMap<Identifier, TokenAmount>` here). If a string key cannot be parsed
// as `u64`, deserialization fails with a serde error. Using `BTreeMap` keeps
// keys ordered by timestamp.
fn deserialize_ts_to_id_amount_map<'de, D>(
deserializer: D,
) -> Result<BTreeMap<TimestampMillis, BTreeMap<Identifier, TokenAmount>>, D::Error>
where
D: Deserializer<'de>,
{
// Untagged enum attempts the variants in order: first try a map with u64
// keys; if that doesn't match, try a map with string keys.
#[derive(Deserialize)]
#[serde(untagged)]
enum U64OrStrTs<V> {
// JSON: { 1735689600000: { <id>: <amount>, ... }, ... }
U64(BTreeMap<TimestampMillis, V>),
// JSON: { "1735689600000": { <id>: <amount>, ... }, ... }
Str(BTreeMap<String, V>),
}

let helper: U64OrStrTs<BTreeMap<Identifier, TokenAmount>> =
U64OrStrTs::deserialize(deserializer)?;
match helper {
// Already has numeric timestamp keys; return as-is
U64OrStrTs::U64(m) => Ok(m),
// Convert string timestamp keys into u64, preserving values unchanged
U64OrStrTs::Str(sm) => sm
.into_iter()
.map(|(k, v)| {
k.parse::<u64>()
.map_err(serde::de::Error::custom)
.map(|ts| (ts, v))
})
.collect(),
}
}
Loading
Loading