dpp/tokens/
token_pricing_schedule.rs

1use crate::balances::credits::TokenAmount;
2use crate::errors::ProtocolError;
3use crate::fee::Credits;
4use bincode::{Decode, Encode};
5use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
6#[cfg(feature = "serde-conversion")]
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9use std::fmt::{self, Display, Formatter};
10
11/// Defines the pricing schedule for tokens in terms of credits.
12///
13/// A pricing schedule can either be a single, flat price applied to all
14/// token amounts, or a tiered pricing model where specific amounts
15/// correspond to specific credit values.
16#[derive(
17    Debug,
18    Clone,
19    Encode,
20    Decode,
21    Eq,
22    PartialEq,
23    Ord,
24    PartialOrd,
25    PlatformSerialize,
26    PlatformDeserialize,
27)]
28#[cfg_attr(feature = "serde-conversion", derive(Serialize, Deserialize))]
29pub enum TokenPricingSchedule {
30    /// A single flat price in credits for all token amounts.
31    ///
32    /// This variant is used when the pricing does not depend on
33    /// the number of tokens being purchased or processed.
34    SinglePrice(Credits),
35
36    /// A tiered pricing model where specific token amounts map to credit prices.
37    ///
38    /// This allows for more complex pricing structures, such as
39    /// volume discounts or progressive pricing. The map keys
40    /// represent token amount thresholds, and the values are the
41    /// corresponding credit prices.
42    /// If the first token amount is greater than 1 this means that the user can only
43    /// purchase that amount as a minimum at a time.
44    SetPrices(BTreeMap<TokenAmount, Credits>),
45}
46
47impl TokenPricingSchedule {
48    pub fn minimum_purchase_amount_and_price(&self) -> (TokenAmount, Credits) {
49        match self {
50            TokenPricingSchedule::SinglePrice(price) => (1, *price),
51            TokenPricingSchedule::SetPrices(prices) => prices
52                .first_key_value()
53                .map(|(amount, cost)| (*amount, *cost))
54                .unwrap_or_default(),
55        }
56    }
57}
58
59impl Display for TokenPricingSchedule {
60    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61        match self {
62            TokenPricingSchedule::SinglePrice(credits) => {
63                write!(f, "SinglePrice: {}", credits)
64            }
65            TokenPricingSchedule::SetPrices(prices) => {
66                write!(f, "SetPrices: [")?;
67                for (i, (amount, credits)) in prices.iter().enumerate() {
68                    if i > 0 {
69                        write!(f, ", ")?;
70                    }
71                    write!(f, "{} => {}", amount, credits)?;
72                }
73                write!(f, "]")
74            }
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn single_price_minimum_purchase_amount_and_price() {
85        let schedule = TokenPricingSchedule::SinglePrice(500);
86        let (amount, price) = schedule.minimum_purchase_amount_and_price();
87        assert_eq!(amount, 1);
88        assert_eq!(price, 500);
89    }
90
91    #[test]
92    fn single_price_zero_credits() {
93        let schedule = TokenPricingSchedule::SinglePrice(0);
94        let (amount, price) = schedule.minimum_purchase_amount_and_price();
95        assert_eq!(amount, 1);
96        assert_eq!(price, 0);
97    }
98
99    #[test]
100    fn set_prices_minimum_purchase_amount_and_price_single_entry() {
101        let mut prices = BTreeMap::new();
102        prices.insert(10u64, 100u64);
103        let schedule = TokenPricingSchedule::SetPrices(prices);
104        let (amount, price) = schedule.minimum_purchase_amount_and_price();
105        assert_eq!(amount, 10);
106        assert_eq!(price, 100);
107    }
108
109    #[test]
110    fn set_prices_minimum_purchase_amount_and_price_multiple_entries() {
111        let mut prices = BTreeMap::new();
112        prices.insert(5u64, 50u64);
113        prices.insert(10u64, 80u64);
114        prices.insert(100u64, 500u64);
115        let schedule = TokenPricingSchedule::SetPrices(prices);
116        // BTreeMap orders by key, so the first entry is the minimum amount
117        let (amount, price) = schedule.minimum_purchase_amount_and_price();
118        assert_eq!(amount, 5);
119        assert_eq!(price, 50);
120    }
121
122    #[test]
123    fn set_prices_empty_map_returns_default() {
124        let prices = BTreeMap::new();
125        let schedule = TokenPricingSchedule::SetPrices(prices);
126        let (amount, price) = schedule.minimum_purchase_amount_and_price();
127        // unwrap_or_default returns (0, 0) for empty map
128        assert_eq!(amount, 0);
129        assert_eq!(price, 0);
130    }
131
132    #[test]
133    fn display_single_price() {
134        let schedule = TokenPricingSchedule::SinglePrice(1234);
135        assert_eq!(format!("{}", schedule), "SinglePrice: 1234");
136    }
137
138    #[test]
139    fn display_set_prices_empty() {
140        let schedule = TokenPricingSchedule::SetPrices(BTreeMap::new());
141        assert_eq!(format!("{}", schedule), "SetPrices: []");
142    }
143
144    #[test]
145    fn display_set_prices_single_entry() {
146        let mut prices = BTreeMap::new();
147        prices.insert(10u64, 100u64);
148        let schedule = TokenPricingSchedule::SetPrices(prices);
149        assert_eq!(format!("{}", schedule), "SetPrices: [10 => 100]");
150    }
151
152    #[test]
153    fn display_set_prices_multiple_entries() {
154        let mut prices = BTreeMap::new();
155        prices.insert(5u64, 50u64);
156        prices.insert(10u64, 80u64);
157        let schedule = TokenPricingSchedule::SetPrices(prices);
158        // BTreeMap iterates in sorted key order
159        assert_eq!(format!("{}", schedule), "SetPrices: [5 => 50, 10 => 80]");
160    }
161}