1use crate::consensus::fee::balance_is_not_enough_error::BalanceIsNotEnoughError;
39use crate::consensus::fee::fee_error::FeeError;
40
41use crate::fee::fee_result::refunds::FeeRefunds;
42use crate::fee::fee_result::BalanceChange::{AddToBalance, NoBalanceChange, RemoveFromBalance};
43use crate::fee::Credits;
44use crate::prelude::UserFeeIncrease;
45use crate::ProtocolError;
46use platform_value::Identifier;
47use std::cmp::Ordering;
48use std::collections::BTreeMap;
49use std::convert::TryFrom;
50
51pub mod refunds;
52
53pub type LifetimeStorageFees = BTreeMap<u16, Credits>;
56
57#[derive(Debug, Clone, Eq, PartialEq, Default)]
59pub struct FeeResult {
60 pub storage_fee: Credits,
62 pub processing_fee: Credits,
64 pub fee_refunds: FeeRefunds,
66 pub removed_bytes_from_system: u32,
68 pub lifetime_storage_fees: LifetimeStorageFees,
73}
74
75impl TryFrom<Vec<FeeResult>> for FeeResult {
76 type Error = ProtocolError;
77 fn try_from(value: Vec<FeeResult>) -> Result<Self, Self::Error> {
78 let mut aggregate_fee_result = FeeResult::default();
79 value
80 .into_iter()
81 .try_for_each(|fee_result| aggregate_fee_result.checked_add_assign(fee_result))?;
82 Ok(aggregate_fee_result)
83 }
84}
85
86impl TryFrom<Vec<Option<FeeResult>>> for FeeResult {
87 type Error = ProtocolError;
88 fn try_from(value: Vec<Option<FeeResult>>) -> Result<Self, Self::Error> {
89 let mut aggregate_fee_result = FeeResult::default();
90 value.into_iter().try_for_each(|fee_result| {
91 if let Some(fee_result) = fee_result {
92 aggregate_fee_result.checked_add_assign(fee_result)
93 } else {
94 Ok(())
95 }
96 })?;
97 Ok(aggregate_fee_result)
98 }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
103pub enum BalanceChange {
104 AddToBalance(Credits),
106 RemoveFromBalance {
108 required_removed_balance: Credits,
110 desired_removed_balance: Credits,
112 },
113 NoBalanceChange,
115}
116
117#[derive(Clone, Debug)]
119pub struct BalanceChangeForIdentity {
120 pub identity_id: Identifier,
122
123 fee_result: FeeResult,
124 change: BalanceChange,
125}
126
127impl BalanceChangeForIdentity {
128 pub fn change(&self) -> &BalanceChange {
130 &self.change
131 }
132
133 pub fn other_refunds(&self) -> BTreeMap<Identifier, Credits> {
135 self.fee_result
136 .fee_refunds
137 .calculate_all_refunds_except_identity(self.identity_id)
138 }
139
140 pub fn into_fee_result(self) -> FeeResult {
142 self.fee_result
143 }
144
145 fn into_fee_result_less_processing_debt(self, processing_debt: u64) -> FeeResult {
147 FeeResult {
148 processing_fee: self.fee_result.processing_fee - processing_debt,
149 ..self.fee_result
150 }
151 }
152
153 pub fn fee_result_outcome<E>(self, user_balance: u64) -> Result<FeeResult, E>
155 where
156 E: From<FeeError>,
157 {
158 match self.change {
159 AddToBalance { .. } => {
160 Ok(self.into_fee_result())
163 }
164 RemoveFromBalance {
165 required_removed_balance,
166 desired_removed_balance,
167 } => {
168 if user_balance >= desired_removed_balance {
169 Ok(self.into_fee_result())
170 } else if user_balance >= required_removed_balance {
171 Ok(self.into_fee_result_less_processing_debt(
174 desired_removed_balance - user_balance,
175 ))
176 } else {
177 Err(
179 FeeError::BalanceIsNotEnoughError(BalanceIsNotEnoughError::new(
180 user_balance,
181 required_removed_balance,
182 ))
183 .into(),
184 )
185 }
186 }
187 NoBalanceChange => {
188 Ok(self.into_fee_result())
190 }
191 }
192 }
193}
194
195impl FeeResult {
196 pub fn new_from_processing_fee(credits: Credits) -> Self {
198 Self {
199 storage_fee: 0,
200 processing_fee: credits,
201 fee_refunds: Default::default(),
202 removed_bytes_from_system: 0,
203 lifetime_storage_fees: Default::default(),
204 }
205 }
206
207 pub fn apply_user_fee_increase(&mut self, add_fee_percentage_multiplier: UserFeeIncrease) {
209 let additional_processing_fee = (self.processing_fee as u128)
210 .saturating_mul(add_fee_percentage_multiplier as u128)
211 .saturating_div(100);
212 if additional_processing_fee > u64::MAX as u128 {
213 self.processing_fee = u64::MAX;
214 } else {
215 self.processing_fee = self
216 .processing_fee
217 .saturating_add(additional_processing_fee as u64);
218 }
219 }
220
221 pub fn total_base_fee(&self) -> Credits {
223 self.storage_fee.saturating_add(self.processing_fee)
224 }
225
226 pub fn into_balance_change(self, identity_id: Identifier) -> BalanceChangeForIdentity {
228 let storage_credits_returned = self
229 .fee_refunds
230 .calculate_refunds_amount_for_identity(identity_id)
231 .unwrap_or_default();
232
233 let base_required_removed_balance = self.storage_fee;
234 let base_desired_removed_balance = self.storage_fee + self.processing_fee;
235
236 let balance_change = match storage_credits_returned.cmp(&base_desired_removed_balance) {
237 Ordering::Less => {
238 let required_removed_balance =
240 base_required_removed_balance.saturating_sub(storage_credits_returned);
241
242 let desired_removed_balance =
243 base_desired_removed_balance - storage_credits_returned;
244
245 RemoveFromBalance {
246 required_removed_balance,
247 desired_removed_balance,
248 }
249 }
250 Ordering::Equal => NoBalanceChange,
251 Ordering::Greater => {
252 AddToBalance(storage_credits_returned - base_desired_removed_balance)
254 }
255 };
256
257 BalanceChangeForIdentity {
258 identity_id,
259 fee_result: self,
260 change: balance_change,
261 }
262 }
263
264 pub fn default_with_fees(storage_fee: Credits, processing_fee: Credits) -> Self {
266 FeeResult {
267 storage_fee,
268 processing_fee,
269 ..Default::default()
270 }
271 }
272
273 pub fn checked_add_assign(&mut self, rhs: Self) -> Result<(), ProtocolError> {
275 self.storage_fee = self
276 .storage_fee
277 .checked_add(rhs.storage_fee)
278 .ok_or(ProtocolError::Overflow("storage fee overflow error"))?;
279 self.processing_fee = self
280 .processing_fee
281 .checked_add(rhs.processing_fee)
282 .ok_or(ProtocolError::Overflow("processing fee overflow error"))?;
283 self.fee_refunds.checked_add_assign(rhs.fee_refunds)?;
284 self.removed_bytes_from_system = self
285 .removed_bytes_from_system
286 .checked_add(rhs.removed_bytes_from_system)
287 .ok_or(ProtocolError::Overflow(
288 "removed_bytes_from_system overflow error",
289 ))?;
290 for (lifetime_epochs, credits) in rhs.lifetime_storage_fees {
291 let lifetime_credits = self
292 .lifetime_storage_fees
293 .entry(lifetime_epochs)
294 .or_default();
295 *lifetime_credits =
296 lifetime_credits
297 .checked_add(credits)
298 .ok_or(ProtocolError::Overflow(
299 "lifetime storage fee overflow error",
300 ))?;
301 }
302 Ok(())
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::consensus::fee::fee_error::FeeError;
310 use crate::fee::epoch::CreditsPerEpoch;
311 use crate::fee::fee_result::refunds::{CreditsPerEpochByIdentifier, FeeRefunds};
312
313 fn make_id(byte: u8) -> Identifier {
314 Identifier::from([byte; 32])
315 }
316
317 fn fee_refunds_for_identity(identity_id: Identifier, credits: Credits) -> FeeRefunds {
319 let mut credits_per_epoch = CreditsPerEpoch::default();
320 credits_per_epoch.insert(0, credits);
321 let mut map = CreditsPerEpochByIdentifier::new();
322 map.insert(*identity_id.as_bytes(), credits_per_epoch);
323 FeeRefunds(map)
324 }
325
326 #[test]
329 fn balance_change_for_identity_change_returns_correct_ref() {
330 let id = make_id(1);
331 let fee_result = FeeResult::default_with_fees(100, 50);
332 let bci = fee_result.into_balance_change(id);
333 match bci.change() {
335 BalanceChange::RemoveFromBalance {
336 required_removed_balance,
337 desired_removed_balance,
338 } => {
339 assert_eq!(*required_removed_balance, 100);
340 assert_eq!(*desired_removed_balance, 150);
341 }
342 other => panic!("Expected RemoveFromBalance, got {:?}", other),
343 }
344 }
345
346 #[test]
349 fn other_refunds_empty_when_no_refunds() {
350 let id = make_id(1);
351 let fee_result = FeeResult::default_with_fees(100, 50);
352 let bci = fee_result.into_balance_change(id);
353 let refunds = bci.other_refunds();
354 assert!(refunds.is_empty());
355 }
356
357 #[test]
358 fn other_refunds_excludes_own_identity() {
359 let id = make_id(1);
360 let other_id = make_id(2);
361 let mut credits_per_epoch_self = CreditsPerEpoch::default();
363 credits_per_epoch_self.insert(0, 200);
364 let mut credits_per_epoch_other = CreditsPerEpoch::default();
365 credits_per_epoch_other.insert(0, 300);
366 let mut map = CreditsPerEpochByIdentifier::new();
367 map.insert(*id.as_bytes(), credits_per_epoch_self);
368 map.insert(*other_id.as_bytes(), credits_per_epoch_other);
369 let refunds = FeeRefunds(map);
370
371 let fee_result = FeeResult {
372 storage_fee: 100,
373 processing_fee: 50,
374 fee_refunds: refunds,
375 removed_bytes_from_system: 0,
376 lifetime_storage_fees: Default::default(),
377 };
378 let bci = fee_result.into_balance_change(id);
379 let other = bci.other_refunds();
380 assert_eq!(other.len(), 1);
381 assert_eq!(*other.get(&other_id).unwrap(), 300);
382 }
383
384 #[test]
387 fn into_fee_result_preserves_original() {
388 let fee_result = FeeResult {
389 storage_fee: 42,
390 processing_fee: 58,
391 fee_refunds: FeeRefunds::default(),
392 removed_bytes_from_system: 10,
393 lifetime_storage_fees: Default::default(),
394 };
395 let id = make_id(1);
396 let bci = fee_result.clone().into_balance_change(id);
397 let recovered = bci.into_fee_result();
398 assert_eq!(recovered.storage_fee, 42);
399 assert_eq!(recovered.processing_fee, 58);
400 assert_eq!(recovered.removed_bytes_from_system, 10);
401 }
402
403 #[test]
406 fn fee_result_outcome_add_to_balance_returns_fee_result() {
407 let id = make_id(1);
408 let refunds = fee_refunds_for_identity(id, 500);
410 let fee_result = FeeResult {
411 storage_fee: 100,
412 processing_fee: 50,
413 fee_refunds: refunds,
414 removed_bytes_from_system: 0,
415 lifetime_storage_fees: Default::default(),
416 };
417 let bci = fee_result.into_balance_change(id);
418 match bci.change() {
419 BalanceChange::AddToBalance(amount) => assert_eq!(*amount, 350),
420 other => panic!("Expected AddToBalance, got {:?}", other),
421 }
422 let refunds2 = fee_refunds_for_identity(id, 500);
424 let fee_result2 = FeeResult {
425 storage_fee: 100,
426 processing_fee: 50,
427 fee_refunds: refunds2,
428 removed_bytes_from_system: 0,
429 lifetime_storage_fees: Default::default(),
430 };
431 let bci2 = fee_result2.into_balance_change(id);
432 let result: Result<FeeResult, FeeError> = bci2.fee_result_outcome(0);
433 assert!(result.is_ok());
434 }
435
436 #[test]
437 fn fee_result_outcome_remove_balance_sufficient_desired() {
438 let id = make_id(1);
439 let fee_result = FeeResult::default_with_fees(100, 50);
440 let bci = fee_result.into_balance_change(id);
441 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(200);
443 let fr = result.unwrap();
444 assert_eq!(fr.storage_fee, 100);
445 assert_eq!(fr.processing_fee, 50);
446 }
447
448 #[test]
449 fn fee_result_outcome_remove_balance_sufficient_required_but_not_desired() {
450 let id = make_id(1);
451 let fee_result = FeeResult::default_with_fees(100, 50);
452 let bci = fee_result.into_balance_change(id);
453 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(120);
455 let fr = result.unwrap();
456 assert_eq!(fr.storage_fee, 100);
457 assert_eq!(fr.processing_fee, 20);
459 }
460
461 #[test]
462 fn fee_result_outcome_remove_balance_insufficient_returns_error() {
463 let id = make_id(1);
464 let fee_result = FeeResult::default_with_fees(100, 50);
465 let bci = fee_result.into_balance_change(id);
466 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(50);
468 assert!(result.is_err());
469 match result.unwrap_err() {
470 FeeError::BalanceIsNotEnoughError(e) => {
471 assert_eq!(e.balance(), 50);
472 assert_eq!(e.fee(), 100);
473 }
474 }
475 }
476
477 #[test]
478 fn fee_result_outcome_no_balance_change_returns_fee_result() {
479 let id = make_id(1);
480 let refunds = fee_refunds_for_identity(id, 150);
482 let fee_result = FeeResult {
483 storage_fee: 100,
484 processing_fee: 50,
485 fee_refunds: refunds,
486 removed_bytes_from_system: 0,
487 lifetime_storage_fees: Default::default(),
488 };
489 let bci = fee_result.into_balance_change(id);
490 match bci.change() {
491 BalanceChange::NoBalanceChange => {}
492 other => panic!("Expected NoBalanceChange, got {:?}", other),
493 }
494 let refunds2 = fee_refunds_for_identity(id, 150);
496 let fee_result2 = FeeResult {
497 storage_fee: 100,
498 processing_fee: 50,
499 fee_refunds: refunds2,
500 removed_bytes_from_system: 0,
501 lifetime_storage_fees: Default::default(),
502 };
503 let bci2 = fee_result2.into_balance_change(id);
504 let result: Result<FeeResult, FeeError> = bci2.fee_result_outcome(0);
505 assert!(result.is_ok());
506 }
507
508 #[test]
511 fn into_balance_change_less_refund_than_fees() {
512 let id = make_id(1);
513 let refunds = fee_refunds_for_identity(id, 50);
515 let fee_result = FeeResult {
516 storage_fee: 100,
517 processing_fee: 50,
518 fee_refunds: refunds,
519 removed_bytes_from_system: 0,
520 lifetime_storage_fees: Default::default(),
521 };
522 let bci = fee_result.into_balance_change(id);
523 match bci.change() {
524 BalanceChange::RemoveFromBalance {
525 required_removed_balance,
526 desired_removed_balance,
527 } => {
528 assert_eq!(*required_removed_balance, 50);
530 assert_eq!(*desired_removed_balance, 100);
532 }
533 other => panic!("Expected RemoveFromBalance, got {:?}", other),
534 }
535 }
536
537 #[test]
538 fn into_balance_change_refund_equals_fees() {
539 let id = make_id(1);
540 let refunds = fee_refunds_for_identity(id, 150);
541 let fee_result = FeeResult {
542 storage_fee: 100,
543 processing_fee: 50,
544 fee_refunds: refunds,
545 removed_bytes_from_system: 0,
546 lifetime_storage_fees: Default::default(),
547 };
548 let bci = fee_result.into_balance_change(id);
549 assert_eq!(bci.change(), &BalanceChange::NoBalanceChange);
550 }
551
552 #[test]
553 fn into_balance_change_refund_greater_than_fees() {
554 let id = make_id(1);
555 let refunds = fee_refunds_for_identity(id, 300);
556 let fee_result = FeeResult {
557 storage_fee: 100,
558 processing_fee: 50,
559 fee_refunds: refunds,
560 removed_bytes_from_system: 0,
561 lifetime_storage_fees: Default::default(),
562 };
563 let bci = fee_result.into_balance_change(id);
564 match bci.change() {
565 BalanceChange::AddToBalance(amount) => {
566 assert_eq!(*amount, 150); }
568 other => panic!("Expected AddToBalance, got {:?}", other),
569 }
570 }
571
572 #[test]
573 fn into_balance_change_no_refunds_no_fees() {
574 let id = make_id(1);
575 let fee_result = FeeResult::default();
576 let bci = fee_result.into_balance_change(id);
577 assert_eq!(bci.change(), &BalanceChange::NoBalanceChange);
579 }
580
581 #[test]
582 fn into_balance_change_no_refunds_with_fees() {
583 let id = make_id(1);
584 let fee_result = FeeResult::default_with_fees(200, 100);
585 let bci = fee_result.into_balance_change(id);
586 match bci.change() {
587 BalanceChange::RemoveFromBalance {
588 required_removed_balance,
589 desired_removed_balance,
590 } => {
591 assert_eq!(*required_removed_balance, 200);
592 assert_eq!(*desired_removed_balance, 300);
593 }
594 other => panic!("Expected RemoveFromBalance, got {:?}", other),
595 }
596 }
597
598 #[test]
601 fn apply_user_fee_increase_zero_percent() {
602 let mut fr = FeeResult::default_with_fees(100, 1000);
603 fr.apply_user_fee_increase(0);
604 assert_eq!(fr.processing_fee, 1000);
605 }
606
607 #[test]
608 fn apply_user_fee_increase_100_percent() {
609 let mut fr = FeeResult::default_with_fees(100, 1000);
610 fr.apply_user_fee_increase(100);
611 assert_eq!(fr.processing_fee, 2000);
613 }
614
615 #[test]
616 fn apply_user_fee_increase_50_percent() {
617 let mut fr = FeeResult::default_with_fees(100, 1000);
618 fr.apply_user_fee_increase(50);
619 assert_eq!(fr.processing_fee, 1500);
621 }
622
623 #[test]
624 fn apply_user_fee_increase_does_not_affect_storage_fee() {
625 let mut fr = FeeResult::default_with_fees(500, 1000);
626 fr.apply_user_fee_increase(100);
627 assert_eq!(fr.storage_fee, 500);
628 assert_eq!(fr.processing_fee, 2000);
629 }
630
631 #[test]
632 fn apply_user_fee_increase_saturates_on_overflow() {
633 let mut fr = FeeResult::default_with_fees(0, u64::MAX);
634 fr.apply_user_fee_increase(100);
635 assert_eq!(fr.processing_fee, u64::MAX);
637 }
638
639 #[test]
640 fn apply_user_fee_increase_1_percent() {
641 let mut fr = FeeResult::default_with_fees(0, 10000);
642 fr.apply_user_fee_increase(1);
643 assert_eq!(fr.processing_fee, 10100);
645 }
646}