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