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