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