1use crate::prelude::BlockHeight;
12use crate::ProtocolError;
13use integer_encoding::VarInt;
14use std::collections::BTreeMap;
15use std::convert::TryFrom;
16
17pub type Duffs = u64;
19
20pub type Credits = u64;
22
23pub type RemainingCredits = Credits;
25
26pub type TokenAmount = u64;
28
29pub type SignedTokenAmount = i64;
31
32pub type SumTokenAmount = i128;
34
35pub type SignedCredits = i64;
38
39pub const MAX_CREDITS: Credits = 9223372036854775807 as Credits; pub const CREDITS_PER_DUFF: Credits = 1000;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, bincode::Encode, bincode::Decode)]
46pub enum CreditOperation {
47 SetCredits(Credits),
49 AddToCredits(Credits),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, bincode::Encode, bincode::Decode)]
55pub enum BlockAwareCreditOperation {
56 SetCredits(Credits),
58 AddToCreditsOperations(BTreeMap<BlockHeight, Credits>),
60}
61
62impl BlockAwareCreditOperation {
63 pub fn merge(&mut self, block_height: BlockHeight, operation: &CreditOperation) {
69 match (self, operation) {
70 (
72 BlockAwareCreditOperation::SetCredits(current),
73 CreditOperation::SetCredits(new_val),
74 ) => {
75 *current = *new_val;
76 }
77 (
79 BlockAwareCreditOperation::SetCredits(current),
80 CreditOperation::AddToCredits(add_val),
81 ) => {
82 *current = current.saturating_add(*add_val);
83 }
84 (
86 this @ BlockAwareCreditOperation::AddToCreditsOperations(_),
87 CreditOperation::SetCredits(new_val),
88 ) => {
89 *this = BlockAwareCreditOperation::SetCredits(*new_val);
92 }
93 (
95 BlockAwareCreditOperation::AddToCreditsOperations(map),
96 CreditOperation::AddToCredits(add_val),
97 ) => {
98 map.entry(block_height)
99 .and_modify(|existing| *existing = existing.saturating_add(*add_val))
100 .or_insert(*add_val);
101 }
102 }
103 }
104
105 pub fn from_operation(block_height: BlockHeight, operation: &CreditOperation) -> Self {
107 match operation {
108 CreditOperation::SetCredits(value) => BlockAwareCreditOperation::SetCredits(*value),
109 CreditOperation::AddToCredits(value) => {
110 let mut map = BTreeMap::new();
111 map.insert(block_height, *value);
112 BlockAwareCreditOperation::AddToCreditsOperations(map)
113 }
114 }
115 }
116}
117
118impl CreditOperation {
119 pub fn merge(&self, other: &CreditOperation) -> CreditOperation {
127 match (self, other) {
128 (_, CreditOperation::SetCredits(value)) => CreditOperation::SetCredits(*value),
130 (CreditOperation::SetCredits(set_val), CreditOperation::AddToCredits(add_val)) => {
132 CreditOperation::SetCredits(set_val.saturating_add(*add_val))
133 }
134 (CreditOperation::AddToCredits(val1), CreditOperation::AddToCredits(val2)) => {
136 CreditOperation::AddToCredits(val1.saturating_add(*val2))
137 }
138 }
139 }
140}
141
142pub trait Creditable {
144 fn to_signed(&self) -> Result<SignedCredits, ProtocolError>;
146 fn to_unsigned(&self) -> Credits;
148
149 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError>
153 where
154 Self: Sized;
155 fn to_vec_bytes(&self) -> Vec<u8>;
157}
158
159impl Creditable for Credits {
160 fn to_signed(&self) -> Result<SignedCredits, ProtocolError> {
161 SignedCredits::try_from(*self)
162 .map_err(|_| ProtocolError::Overflow("credits are too big to convert to signed value"))
163 }
164
165 fn to_unsigned(&self) -> Credits {
166 *self
167 }
168
169 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError> {
170 Self::decode_var(vec.as_slice()).map(|(n, _)| n).ok_or(
171 ProtocolError::CorruptedSerialization(
172 "pending refunds epoch index for must be u16".to_string(),
173 ),
174 )
175 }
176
177 fn to_vec_bytes(&self) -> Vec<u8> {
178 self.encode_var_vec()
179 }
180}
181
182impl Creditable for SignedCredits {
183 fn to_signed(&self) -> Result<SignedCredits, ProtocolError> {
184 Ok(*self)
185 }
186
187 fn to_unsigned(&self) -> Credits {
188 self.unsigned_abs()
189 }
190
191 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError> {
192 Self::decode_var(vec.as_slice()).map(|(n, _)| n).ok_or(
193 ProtocolError::CorruptedSerialization(
194 "pending refunds epoch index for must be u16".to_string(),
195 ),
196 )
197 }
198
199 fn to_vec_bytes(&self) -> Vec<u8> {
200 self.encode_var_vec()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 mod block_aware_credit_operation {
209 use super::*;
210
211 #[test]
212 fn from_operation_set_credits() {
213 let op =
214 BlockAwareCreditOperation::from_operation(100, &CreditOperation::SetCredits(1000));
215 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1000));
216 }
217
218 #[test]
219 fn from_operation_add_to_credits() {
220 let op =
221 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
222 let expected: BTreeMap<BlockHeight, Credits> = [(100, 500)].into_iter().collect();
223 assert_eq!(
224 op,
225 BlockAwareCreditOperation::AddToCreditsOperations(expected)
226 );
227 }
228
229 #[test]
230 fn merge_set_then_set_takes_latest() {
231 let mut op = BlockAwareCreditOperation::SetCredits(1000);
232 op.merge(101, &CreditOperation::SetCredits(2000));
233 assert_eq!(op, BlockAwareCreditOperation::SetCredits(2000));
234 }
235
236 #[test]
237 fn merge_set_then_add_adds_to_set() {
238 let mut op = BlockAwareCreditOperation::SetCredits(1000);
239 op.merge(101, &CreditOperation::AddToCredits(500));
240 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1500));
241 }
242
243 #[test]
244 fn merge_set_then_multiple_adds() {
245 let mut op = BlockAwareCreditOperation::SetCredits(1000);
246 op.merge(101, &CreditOperation::AddToCredits(500));
247 op.merge(102, &CreditOperation::AddToCredits(300));
248 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1800));
249 }
250
251 #[test]
252 fn merge_add_then_set_becomes_set() {
253 let mut op =
254 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
255 op.merge(101, &CreditOperation::SetCredits(2000));
256 assert_eq!(op, BlockAwareCreditOperation::SetCredits(2000));
257 }
258
259 #[test]
260 fn merge_add_then_add_preserves_block_heights() {
261 let mut op =
262 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
263 op.merge(101, &CreditOperation::AddToCredits(300));
264 op.merge(102, &CreditOperation::AddToCredits(200));
265
266 let expected: BTreeMap<BlockHeight, Credits> =
267 [(100, 500), (101, 300), (102, 200)].into_iter().collect();
268 assert_eq!(
269 op,
270 BlockAwareCreditOperation::AddToCreditsOperations(expected)
271 );
272 }
273
274 #[test]
275 fn merge_multiple_adds_at_same_block_combines() {
276 let mut op =
277 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
278 op.merge(100, &CreditOperation::AddToCredits(300)); let expected: BTreeMap<BlockHeight, Credits> = [(100, 800)].into_iter().collect();
281 assert_eq!(
282 op,
283 BlockAwareCreditOperation::AddToCreditsOperations(expected)
284 );
285 }
286
287 #[test]
288 fn merge_add_then_set_then_add() {
289 let mut op =
291 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
292 op.merge(101, &CreditOperation::SetCredits(1000));
294 op.merge(102, &CreditOperation::AddToCredits(200));
296
297 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1200));
299 }
300
301 #[test]
302 fn client_sync_scenario() {
303 let mut op =
308 BlockAwareCreditOperation::from_operation(400, &CreditOperation::AddToCredits(100));
309 op.merge(450, &CreditOperation::AddToCredits(200));
310 op.merge(500, &CreditOperation::AddToCredits(300));
311 op.merge(550, &CreditOperation::AddToCredits(400));
312 op.merge(600, &CreditOperation::AddToCredits(500));
313
314 if let BlockAwareCreditOperation::AddToCreditsOperations(map) = &op {
316 assert_eq!(map.len(), 5);
317
318 let to_apply: Credits = map
320 .iter()
321 .filter(|(block, _)| **block > 550)
322 .map(|(_, credits)| *credits)
323 .sum();
324
325 assert_eq!(to_apply, 500);
327
328 let to_apply_from_400: Credits = map
330 .iter()
331 .filter(|(block, _)| **block > 400)
332 .map(|(_, credits)| *credits)
333 .sum();
334
335 assert_eq!(to_apply_from_400, 1400);
337 } else {
338 panic!("Expected AddToCreditsOperations");
339 }
340 }
341
342 #[test]
343 fn set_credits_followed_by_adds_scenario() {
344 let mut op =
350 BlockAwareCreditOperation::from_operation(400, &CreditOperation::SetCredits(10000));
351 op.merge(500, &CreditOperation::AddToCredits(100));
352 op.merge(600, &CreditOperation::AddToCredits(200));
353
354 assert_eq!(op, BlockAwareCreditOperation::SetCredits(10300));
356
357 }
360 }
361
362 #[test]
367 fn credits_to_signed_within_range() {
368 let credits: Credits = 1000;
369 let result = credits.to_signed();
370 assert!(result.is_ok());
371 assert_eq!(result.unwrap(), 1000i64);
372 }
373
374 #[test]
375 fn credits_to_signed_zero() {
376 let credits: Credits = 0;
377 let result = credits.to_signed();
378 assert!(result.is_ok());
379 assert_eq!(result.unwrap(), 0i64);
380 }
381
382 #[test]
383 fn credits_to_signed_max_i64() {
384 let credits: Credits = i64::MAX as u64;
385 let result = credits.to_signed();
386 assert!(result.is_ok());
387 assert_eq!(result.unwrap(), i64::MAX);
388 }
389
390 #[test]
391 fn credits_to_signed_overflow() {
392 let credits: Credits = u64::MAX;
394 let result = credits.to_signed();
395 assert!(result.is_err());
396 match result.unwrap_err() {
397 ProtocolError::Overflow(msg) => {
398 assert!(msg.contains("too big"));
399 }
400 other => panic!("Expected Overflow error, got: {:?}", other),
401 }
402 }
403
404 #[test]
405 fn credits_to_signed_just_over_i64_max() {
406 let credits: Credits = (i64::MAX as u64) + 1;
408 let result = credits.to_signed();
409 assert!(result.is_err());
410 }
411
412 #[test]
417 fn credits_to_unsigned_returns_self() {
418 let credits: Credits = 42;
419 assert_eq!(credits.to_unsigned(), 42);
420 }
421
422 #[test]
423 fn credits_to_unsigned_zero() {
424 let credits: Credits = 0;
425 assert_eq!(credits.to_unsigned(), 0);
426 }
427
428 #[test]
429 fn credits_to_unsigned_max() {
430 let credits: Credits = u64::MAX;
431 assert_eq!(credits.to_unsigned(), u64::MAX);
432 }
433
434 #[test]
439 fn signed_credits_to_signed_returns_self() {
440 let sc: SignedCredits = -500;
441 assert_eq!(sc.to_signed().unwrap(), -500);
442 }
443
444 #[test]
445 fn signed_credits_to_unsigned_returns_abs() {
446 let sc: SignedCredits = -500;
447 assert_eq!(sc.to_unsigned(), 500);
448
449 let sc_pos: SignedCredits = 500;
450 assert_eq!(sc_pos.to_unsigned(), 500);
451 }
452
453 #[test]
454 fn signed_credits_to_unsigned_zero() {
455 let sc: SignedCredits = 0;
456 assert_eq!(sc.to_unsigned(), 0);
457 }
458
459 #[test]
464 fn credits_roundtrip_zero() {
465 let original: Credits = 0;
466 let bytes = original.to_vec_bytes();
467 let decoded = Credits::from_vec_bytes(bytes).unwrap();
468 assert_eq!(decoded, original);
469 }
470
471 #[test]
472 fn credits_roundtrip_one() {
473 let original: Credits = 1;
474 let bytes = original.to_vec_bytes();
475 let decoded = Credits::from_vec_bytes(bytes).unwrap();
476 assert_eq!(decoded, original);
477 }
478
479 #[test]
480 fn credits_roundtrip_max() {
481 let original: Credits = u64::MAX;
482 let bytes = original.to_vec_bytes();
483 let decoded = Credits::from_vec_bytes(bytes).unwrap();
484 assert_eq!(decoded, original);
485 }
486
487 #[test]
488 fn credits_roundtrip_large_value() {
489 let original: Credits = 1_000_000_000_000;
490 let bytes = original.to_vec_bytes();
491 let decoded = Credits::from_vec_bytes(bytes).unwrap();
492 assert_eq!(decoded, original);
493 }
494
495 #[test]
496 fn credits_roundtrip_max_credits_constant() {
497 let original: Credits = MAX_CREDITS;
498 let bytes = original.to_vec_bytes();
499 let decoded = Credits::from_vec_bytes(bytes).unwrap();
500 assert_eq!(decoded, original);
501 }
502
503 #[test]
504 fn credits_from_vec_bytes_empty_vec_error() {
505 let result = Credits::from_vec_bytes(vec![]);
506 assert!(result.is_err());
507 }
508
509 #[test]
514 fn signed_credits_roundtrip_zero() {
515 let original: SignedCredits = 0;
516 let bytes = original.to_vec_bytes();
517 let decoded = SignedCredits::from_vec_bytes(bytes).unwrap();
518 assert_eq!(decoded, original);
519 }
520
521 #[test]
522 fn signed_credits_roundtrip_positive() {
523 let original: SignedCredits = 123456789;
524 let bytes = original.to_vec_bytes();
525 let decoded = SignedCredits::from_vec_bytes(bytes).unwrap();
526 assert_eq!(decoded, original);
527 }
528
529 #[test]
530 fn signed_credits_roundtrip_negative() {
531 let original: SignedCredits = -987654321;
532 let bytes = original.to_vec_bytes();
533 let decoded = SignedCredits::from_vec_bytes(bytes).unwrap();
534 assert_eq!(decoded, original);
535 }
536
537 #[test]
538 fn signed_credits_roundtrip_max() {
539 let original: SignedCredits = i64::MAX;
540 let bytes = original.to_vec_bytes();
541 let decoded = SignedCredits::from_vec_bytes(bytes).unwrap();
542 assert_eq!(decoded, original);
543 }
544
545 #[test]
546 fn signed_credits_roundtrip_min() {
547 let original: SignedCredits = i64::MIN;
548 let bytes = original.to_vec_bytes();
549 let decoded = SignedCredits::from_vec_bytes(bytes).unwrap();
550 assert_eq!(decoded, original);
551 }
552
553 #[test]
554 fn signed_credits_from_vec_bytes_empty_vec_error() {
555 let result = SignedCredits::from_vec_bytes(vec![]);
556 assert!(result.is_err());
557 }
558
559 #[test]
564 fn max_credits_equals_i64_max() {
565 assert_eq!(MAX_CREDITS, i64::MAX as u64);
566 }
567
568 #[test]
573 fn credit_operation_merge_set_set() {
574 let a = CreditOperation::SetCredits(100);
575 let b = CreditOperation::SetCredits(200);
576 assert_eq!(a.merge(&b), CreditOperation::SetCredits(200));
577 }
578
579 #[test]
580 fn credit_operation_merge_set_add() {
581 let a = CreditOperation::SetCredits(100);
582 let b = CreditOperation::AddToCredits(50);
583 assert_eq!(a.merge(&b), CreditOperation::SetCredits(150));
584 }
585
586 #[test]
587 fn credit_operation_merge_add_set() {
588 let a = CreditOperation::AddToCredits(100);
589 let b = CreditOperation::SetCredits(200);
590 assert_eq!(a.merge(&b), CreditOperation::SetCredits(200));
591 }
592
593 #[test]
594 fn credit_operation_merge_add_add() {
595 let a = CreditOperation::AddToCredits(100);
596 let b = CreditOperation::AddToCredits(50);
597 assert_eq!(a.merge(&b), CreditOperation::AddToCredits(150));
598 }
599
600 #[test]
601 fn credit_operation_merge_set_add_saturating() {
602 let a = CreditOperation::SetCredits(u64::MAX);
603 let b = CreditOperation::AddToCredits(1);
604 assert_eq!(a.merge(&b), CreditOperation::SetCredits(u64::MAX));
606 }
607
608 #[test]
609 fn credit_operation_merge_add_add_saturating() {
610 let a = CreditOperation::AddToCredits(u64::MAX);
611 let b = CreditOperation::AddToCredits(1);
612 assert_eq!(a.merge(&b), CreditOperation::AddToCredits(u64::MAX));
613 }
614
615 #[test]
620 fn credits_per_duff_is_1000() {
621 assert_eq!(CREDITS_PER_DUFF, 1000);
622 }
623}