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#[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(
29 feature = "serde-conversion",
30 derive(Serialize, Deserialize),
31 serde(into = "TokenPricingScheduleRepr", from = "TokenPricingScheduleRepr")
32)]
33pub enum TokenPricingSchedule {
34 SinglePrice(Credits),
39
40 SetPrices(BTreeMap<TokenAmount, Credits>),
49}
50
51#[cfg(feature = "serde-conversion")]
58#[derive(Serialize, Deserialize)]
59#[serde(tag = "$type", rename_all = "camelCase")]
60enum TokenPricingScheduleRepr {
61 SinglePrice {
62 #[cfg_attr(
63 feature = "json-conversion",
64 serde(with = "crate::serialization::json_safe_u64")
65 )]
66 price: Credits,
67 },
68 SetPrices {
69 #[cfg_attr(
70 feature = "json-conversion",
71 serde(with = "crate::serialization::json::safe_integer_map::json_safe_u64_u64_map")
72 )]
73 prices: BTreeMap<TokenAmount, Credits>,
74 },
75}
76
77#[cfg(feature = "serde-conversion")]
78impl From<TokenPricingSchedule> for TokenPricingScheduleRepr {
79 fn from(schedule: TokenPricingSchedule) -> Self {
80 match schedule {
81 TokenPricingSchedule::SinglePrice(price) => Self::SinglePrice { price },
82 TokenPricingSchedule::SetPrices(prices) => Self::SetPrices { prices },
83 }
84 }
85}
86
87#[cfg(feature = "serde-conversion")]
88impl From<TokenPricingScheduleRepr> for TokenPricingSchedule {
89 fn from(repr: TokenPricingScheduleRepr) -> Self {
90 match repr {
91 TokenPricingScheduleRepr::SinglePrice { price } => Self::SinglePrice(price),
92 TokenPricingScheduleRepr::SetPrices { prices } => Self::SetPrices(prices),
93 }
94 }
95}
96
97#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
98impl crate::serialization::JsonConvertible for TokenPricingSchedule {}
99
100#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
101impl crate::serialization::ValueConvertible for TokenPricingSchedule {}
102
103impl TokenPricingSchedule {
104 pub fn minimum_purchase_amount_and_price(&self) -> (TokenAmount, Credits) {
105 match self {
106 TokenPricingSchedule::SinglePrice(price) => (1, *price),
107 TokenPricingSchedule::SetPrices(prices) => prices
108 .first_key_value()
109 .map(|(amount, cost)| (*amount, *cost))
110 .unwrap_or_default(),
111 }
112 }
113}
114
115impl Display for TokenPricingSchedule {
116 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117 match self {
118 TokenPricingSchedule::SinglePrice(credits) => {
119 write!(f, "SinglePrice: {}", credits)
120 }
121 TokenPricingSchedule::SetPrices(prices) => {
122 write!(f, "SetPrices: [")?;
123 for (i, (amount, credits)) in prices.iter().enumerate() {
124 if i > 0 {
125 write!(f, ", ")?;
126 }
127 write!(f, "{} => {}", amount, credits)?;
128 }
129 write!(f, "]")
130 }
131 }
132 }
133}
134
135#[cfg(all(
136 test,
137 feature = "json-conversion",
138 feature = "value-conversion",
139 feature = "serde-conversion"
140))]
141mod json_convertible_tests {
142 use super::*;
143 use platform_value::{platform_value, Value};
144 use serde_json::json;
145
146 #[test]
153 fn json_round_trip_single_price() {
154 use crate::serialization::JsonConvertible;
155 let original = TokenPricingSchedule::SinglePrice(1234);
156 let json = original.to_json().expect("to_json");
157 assert_eq!(json, json!({ "$type": "singlePrice", "price": 1234 }));
158 let recovered = TokenPricingSchedule::from_json(json).expect("from_json");
159 assert_eq!(original, recovered);
160 }
161
162 #[test]
163 fn json_single_price_above_max_safe_integer_is_string() {
164 use crate::serialization::JsonConvertible;
165 let big: Credits = (1u64 << 53) + 1;
169 let original = TokenPricingSchedule::SinglePrice(big);
170 let json = original.to_json().expect("to_json");
171 assert_eq!(
172 json,
173 json!({ "$type": "singlePrice", "price": big.to_string() })
174 );
175 let recovered = TokenPricingSchedule::from_json(json).expect("from_json");
176 assert_eq!(original, recovered);
177 }
178
179 #[test]
180 fn json_round_trip_set_prices() {
181 use crate::serialization::JsonConvertible;
182 let mut prices = BTreeMap::new();
183 prices.insert(5u64, 50u64);
184 prices.insert(10u64, 80u64);
185 let original = TokenPricingSchedule::SetPrices(prices);
186 let json = original.to_json().expect("to_json");
187 assert_eq!(
190 json,
191 json!({ "$type": "setPrices", "prices": { "5": 50, "10": 80 } })
192 );
193 let recovered = TokenPricingSchedule::from_json(json).expect("from_json");
194 assert_eq!(original, recovered);
195 }
196
197 #[test]
198 fn value_round_trip_single_price() {
199 use crate::serialization::ValueConvertible;
200 let original = TokenPricingSchedule::SinglePrice(1234);
201 let value = original.to_object().expect("to_object");
202 assert_eq!(
204 value,
205 platform_value!({ "$type": "singlePrice", "price": 1234u64 })
206 );
207 let recovered = TokenPricingSchedule::from_object(value).expect("from_object");
208 assert_eq!(original, recovered);
209 }
210
211 #[test]
212 fn value_round_trip_set_prices() {
213 use crate::serialization::ValueConvertible;
214 let mut prices = BTreeMap::new();
215 prices.insert(5u64, 50u64);
216 prices.insert(10u64, 80u64);
217 let original = TokenPricingSchedule::SetPrices(prices);
218 let value = original.to_object().expect("to_object");
219 assert_eq!(
222 value,
223 Value::Map(vec![
224 (
225 Value::Text("$type".to_string()),
226 Value::Text("setPrices".to_string()),
227 ),
228 (
229 Value::Text("prices".to_string()),
230 Value::Map(vec![
231 (Value::U64(5), Value::U64(50)),
232 (Value::U64(10), Value::U64(80)),
233 ]),
234 ),
235 ])
236 );
237 let recovered = TokenPricingSchedule::from_object(value).expect("from_object");
238 assert_eq!(original, recovered);
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn single_price_minimum_purchase_amount_and_price() {
248 let schedule = TokenPricingSchedule::SinglePrice(500);
249 let (amount, price) = schedule.minimum_purchase_amount_and_price();
250 assert_eq!(amount, 1);
251 assert_eq!(price, 500);
252 }
253
254 #[test]
255 fn single_price_zero_credits() {
256 let schedule = TokenPricingSchedule::SinglePrice(0);
257 let (amount, price) = schedule.minimum_purchase_amount_and_price();
258 assert_eq!(amount, 1);
259 assert_eq!(price, 0);
260 }
261
262 #[test]
263 fn set_prices_minimum_purchase_amount_and_price_single_entry() {
264 let mut prices = BTreeMap::new();
265 prices.insert(10u64, 100u64);
266 let schedule = TokenPricingSchedule::SetPrices(prices);
267 let (amount, price) = schedule.minimum_purchase_amount_and_price();
268 assert_eq!(amount, 10);
269 assert_eq!(price, 100);
270 }
271
272 #[test]
273 fn set_prices_minimum_purchase_amount_and_price_multiple_entries() {
274 let mut prices = BTreeMap::new();
275 prices.insert(5u64, 50u64);
276 prices.insert(10u64, 80u64);
277 prices.insert(100u64, 500u64);
278 let schedule = TokenPricingSchedule::SetPrices(prices);
279 let (amount, price) = schedule.minimum_purchase_amount_and_price();
281 assert_eq!(amount, 5);
282 assert_eq!(price, 50);
283 }
284
285 #[test]
286 fn set_prices_empty_map_returns_default() {
287 let prices = BTreeMap::new();
288 let schedule = TokenPricingSchedule::SetPrices(prices);
289 let (amount, price) = schedule.minimum_purchase_amount_and_price();
290 assert_eq!(amount, 0);
292 assert_eq!(price, 0);
293 }
294
295 #[test]
296 fn display_single_price() {
297 let schedule = TokenPricingSchedule::SinglePrice(1234);
298 assert_eq!(format!("{}", schedule), "SinglePrice: 1234");
299 }
300
301 #[test]
302 fn display_set_prices_empty() {
303 let schedule = TokenPricingSchedule::SetPrices(BTreeMap::new());
304 assert_eq!(format!("{}", schedule), "SetPrices: []");
305 }
306
307 #[test]
308 fn display_set_prices_single_entry() {
309 let mut prices = BTreeMap::new();
310 prices.insert(10u64, 100u64);
311 let schedule = TokenPricingSchedule::SetPrices(prices);
312 assert_eq!(format!("{}", schedule), "SetPrices: [10 => 100]");
313 }
314
315 #[test]
316 fn display_set_prices_multiple_entries() {
317 let mut prices = BTreeMap::new();
318 prices.insert(5u64, 50u64);
319 prices.insert(10u64, 80u64);
320 let schedule = TokenPricingSchedule::SetPrices(prices);
321 assert_eq!(format!("{}", schedule), "SetPrices: [5 => 50, 10 => 80]");
323 }
324}