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
53#[derive(Debug, Clone, Eq, PartialEq, Default)]
55pub struct FeeResult {
56 pub storage_fee: Credits,
58 pub processing_fee: Credits,
60 pub fee_refunds: FeeRefunds,
62 pub removed_bytes_from_system: u32,
64}
65
66impl TryFrom<Vec<FeeResult>> for FeeResult {
67 type Error = ProtocolError;
68 fn try_from(value: Vec<FeeResult>) -> Result<Self, Self::Error> {
69 let mut aggregate_fee_result = FeeResult::default();
70 value
71 .into_iter()
72 .try_for_each(|fee_result| aggregate_fee_result.checked_add_assign(fee_result))?;
73 Ok(aggregate_fee_result)
74 }
75}
76
77impl TryFrom<Vec<Option<FeeResult>>> for FeeResult {
78 type Error = ProtocolError;
79 fn try_from(value: Vec<Option<FeeResult>>) -> Result<Self, Self::Error> {
80 let mut aggregate_fee_result = FeeResult::default();
81 value.into_iter().try_for_each(|fee_result| {
82 if let Some(fee_result) = fee_result {
83 aggregate_fee_result.checked_add_assign(fee_result)
84 } else {
85 Ok(())
86 }
87 })?;
88 Ok(aggregate_fee_result)
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
94pub enum BalanceChange {
95 AddToBalance(Credits),
97 RemoveFromBalance {
99 required_removed_balance: Credits,
101 desired_removed_balance: Credits,
103 },
104 NoBalanceChange,
106}
107
108#[derive(Clone, Debug)]
110pub struct BalanceChangeForIdentity {
111 pub identity_id: Identifier,
113
114 fee_result: FeeResult,
115 change: BalanceChange,
116}
117
118impl BalanceChangeForIdentity {
119 pub fn change(&self) -> &BalanceChange {
121 &self.change
122 }
123
124 pub fn other_refunds(&self) -> BTreeMap<Identifier, Credits> {
126 self.fee_result
127 .fee_refunds
128 .calculate_all_refunds_except_identity(self.identity_id)
129 }
130
131 pub fn into_fee_result(self) -> FeeResult {
133 self.fee_result
134 }
135
136 fn into_fee_result_less_processing_debt(self, processing_debt: u64) -> FeeResult {
138 FeeResult {
139 processing_fee: self.fee_result.processing_fee - processing_debt,
140 ..self.fee_result
141 }
142 }
143
144 pub fn fee_result_outcome<E>(self, user_balance: u64) -> Result<FeeResult, E>
146 where
147 E: From<FeeError>,
148 {
149 match self.change {
150 AddToBalance { .. } => {
151 Ok(self.into_fee_result())
154 }
155 RemoveFromBalance {
156 required_removed_balance,
157 desired_removed_balance,
158 } => {
159 if user_balance >= desired_removed_balance {
160 Ok(self.into_fee_result())
161 } else if user_balance >= required_removed_balance {
162 Ok(self.into_fee_result_less_processing_debt(
165 desired_removed_balance - user_balance,
166 ))
167 } else {
168 Err(
170 FeeError::BalanceIsNotEnoughError(BalanceIsNotEnoughError::new(
171 user_balance,
172 required_removed_balance,
173 ))
174 .into(),
175 )
176 }
177 }
178 NoBalanceChange => {
179 Ok(self.into_fee_result())
181 }
182 }
183 }
184}
185
186impl FeeResult {
187 pub fn new_from_processing_fee(credits: Credits) -> Self {
189 Self {
190 storage_fee: 0,
191 processing_fee: credits,
192 fee_refunds: Default::default(),
193 removed_bytes_from_system: 0,
194 }
195 }
196
197 pub fn apply_user_fee_increase(&mut self, add_fee_percentage_multiplier: UserFeeIncrease) {
199 let additional_processing_fee = (self.processing_fee as u128)
200 .saturating_mul(add_fee_percentage_multiplier as u128)
201 .saturating_div(100);
202 if additional_processing_fee > u64::MAX as u128 {
203 self.processing_fee = u64::MAX;
204 } else {
205 self.processing_fee = self
206 .processing_fee
207 .saturating_add(additional_processing_fee as u64);
208 }
209 }
210
211 pub fn total_base_fee(&self) -> Credits {
213 self.storage_fee.saturating_add(self.processing_fee)
214 }
215
216 pub fn into_balance_change(self, identity_id: Identifier) -> BalanceChangeForIdentity {
218 let storage_credits_returned = self
219 .fee_refunds
220 .calculate_refunds_amount_for_identity(identity_id)
221 .unwrap_or_default();
222
223 let base_required_removed_balance = self.storage_fee;
224 let base_desired_removed_balance = self.storage_fee + self.processing_fee;
225
226 let balance_change = match storage_credits_returned.cmp(&base_desired_removed_balance) {
227 Ordering::Less => {
228 let required_removed_balance =
230 base_required_removed_balance.saturating_sub(storage_credits_returned);
231
232 let desired_removed_balance =
233 base_desired_removed_balance - storage_credits_returned;
234
235 RemoveFromBalance {
236 required_removed_balance,
237 desired_removed_balance,
238 }
239 }
240 Ordering::Equal => NoBalanceChange,
241 Ordering::Greater => {
242 AddToBalance(storage_credits_returned - base_desired_removed_balance)
244 }
245 };
246
247 BalanceChangeForIdentity {
248 identity_id,
249 fee_result: self,
250 change: balance_change,
251 }
252 }
253
254 pub fn default_with_fees(storage_fee: Credits, processing_fee: Credits) -> Self {
256 FeeResult {
257 storage_fee,
258 processing_fee,
259 ..Default::default()
260 }
261 }
262
263 pub fn checked_add_assign(&mut self, rhs: Self) -> Result<(), ProtocolError> {
265 self.storage_fee = self
266 .storage_fee
267 .checked_add(rhs.storage_fee)
268 .ok_or(ProtocolError::Overflow("storage fee overflow error"))?;
269 self.processing_fee = self
270 .processing_fee
271 .checked_add(rhs.processing_fee)
272 .ok_or(ProtocolError::Overflow("processing fee overflow error"))?;
273 self.fee_refunds.checked_add_assign(rhs.fee_refunds)?;
274 self.removed_bytes_from_system = self
275 .removed_bytes_from_system
276 .checked_add(rhs.removed_bytes_from_system)
277 .ok_or(ProtocolError::Overflow(
278 "removed_bytes_from_system overflow error",
279 ))?;
280 Ok(())
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::consensus::fee::fee_error::FeeError;
288 use crate::fee::epoch::CreditsPerEpoch;
289 use crate::fee::fee_result::refunds::{CreditsPerEpochByIdentifier, FeeRefunds};
290
291 fn make_id(byte: u8) -> Identifier {
292 Identifier::from([byte; 32])
293 }
294
295 fn fee_refunds_for_identity(identity_id: Identifier, credits: Credits) -> FeeRefunds {
297 let mut credits_per_epoch = CreditsPerEpoch::default();
298 credits_per_epoch.insert(0, credits);
299 let mut map = CreditsPerEpochByIdentifier::new();
300 map.insert(*identity_id.as_bytes(), credits_per_epoch);
301 FeeRefunds(map)
302 }
303
304 #[test]
307 fn balance_change_for_identity_change_returns_correct_ref() {
308 let id = make_id(1);
309 let fee_result = FeeResult::default_with_fees(100, 50);
310 let bci = fee_result.into_balance_change(id);
311 match bci.change() {
313 BalanceChange::RemoveFromBalance {
314 required_removed_balance,
315 desired_removed_balance,
316 } => {
317 assert_eq!(*required_removed_balance, 100);
318 assert_eq!(*desired_removed_balance, 150);
319 }
320 other => panic!("Expected RemoveFromBalance, got {:?}", other),
321 }
322 }
323
324 #[test]
327 fn other_refunds_empty_when_no_refunds() {
328 let id = make_id(1);
329 let fee_result = FeeResult::default_with_fees(100, 50);
330 let bci = fee_result.into_balance_change(id);
331 let refunds = bci.other_refunds();
332 assert!(refunds.is_empty());
333 }
334
335 #[test]
336 fn other_refunds_excludes_own_identity() {
337 let id = make_id(1);
338 let other_id = make_id(2);
339 let mut credits_per_epoch_self = CreditsPerEpoch::default();
341 credits_per_epoch_self.insert(0, 200);
342 let mut credits_per_epoch_other = CreditsPerEpoch::default();
343 credits_per_epoch_other.insert(0, 300);
344 let mut map = CreditsPerEpochByIdentifier::new();
345 map.insert(*id.as_bytes(), credits_per_epoch_self);
346 map.insert(*other_id.as_bytes(), credits_per_epoch_other);
347 let refunds = FeeRefunds(map);
348
349 let fee_result = FeeResult {
350 storage_fee: 100,
351 processing_fee: 50,
352 fee_refunds: refunds,
353 removed_bytes_from_system: 0,
354 };
355 let bci = fee_result.into_balance_change(id);
356 let other = bci.other_refunds();
357 assert_eq!(other.len(), 1);
358 assert_eq!(*other.get(&other_id).unwrap(), 300);
359 }
360
361 #[test]
364 fn into_fee_result_preserves_original() {
365 let fee_result = FeeResult {
366 storage_fee: 42,
367 processing_fee: 58,
368 fee_refunds: FeeRefunds::default(),
369 removed_bytes_from_system: 10,
370 };
371 let id = make_id(1);
372 let bci = fee_result.clone().into_balance_change(id);
373 let recovered = bci.into_fee_result();
374 assert_eq!(recovered.storage_fee, 42);
375 assert_eq!(recovered.processing_fee, 58);
376 assert_eq!(recovered.removed_bytes_from_system, 10);
377 }
378
379 #[test]
382 fn fee_result_outcome_add_to_balance_returns_fee_result() {
383 let id = make_id(1);
384 let refunds = fee_refunds_for_identity(id, 500);
386 let fee_result = FeeResult {
387 storage_fee: 100,
388 processing_fee: 50,
389 fee_refunds: refunds,
390 removed_bytes_from_system: 0,
391 };
392 let bci = fee_result.into_balance_change(id);
393 match bci.change() {
394 BalanceChange::AddToBalance(amount) => assert_eq!(*amount, 350),
395 other => panic!("Expected AddToBalance, got {:?}", other),
396 }
397 let refunds2 = fee_refunds_for_identity(id, 500);
399 let fee_result2 = FeeResult {
400 storage_fee: 100,
401 processing_fee: 50,
402 fee_refunds: refunds2,
403 removed_bytes_from_system: 0,
404 };
405 let bci2 = fee_result2.into_balance_change(id);
406 let result: Result<FeeResult, FeeError> = bci2.fee_result_outcome(0);
407 assert!(result.is_ok());
408 }
409
410 #[test]
411 fn fee_result_outcome_remove_balance_sufficient_desired() {
412 let id = make_id(1);
413 let fee_result = FeeResult::default_with_fees(100, 50);
414 let bci = fee_result.into_balance_change(id);
415 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(200);
417 let fr = result.unwrap();
418 assert_eq!(fr.storage_fee, 100);
419 assert_eq!(fr.processing_fee, 50);
420 }
421
422 #[test]
423 fn fee_result_outcome_remove_balance_sufficient_required_but_not_desired() {
424 let id = make_id(1);
425 let fee_result = FeeResult::default_with_fees(100, 50);
426 let bci = fee_result.into_balance_change(id);
427 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(120);
429 let fr = result.unwrap();
430 assert_eq!(fr.storage_fee, 100);
431 assert_eq!(fr.processing_fee, 20);
433 }
434
435 #[test]
436 fn fee_result_outcome_remove_balance_insufficient_returns_error() {
437 let id = make_id(1);
438 let fee_result = FeeResult::default_with_fees(100, 50);
439 let bci = fee_result.into_balance_change(id);
440 let result: Result<FeeResult, FeeError> = bci.fee_result_outcome(50);
442 assert!(result.is_err());
443 match result.unwrap_err() {
444 FeeError::BalanceIsNotEnoughError(e) => {
445 assert_eq!(e.balance(), 50);
446 assert_eq!(e.fee(), 100);
447 }
448 }
449 }
450
451 #[test]
452 fn fee_result_outcome_no_balance_change_returns_fee_result() {
453 let id = make_id(1);
454 let refunds = fee_refunds_for_identity(id, 150);
456 let fee_result = FeeResult {
457 storage_fee: 100,
458 processing_fee: 50,
459 fee_refunds: refunds,
460 removed_bytes_from_system: 0,
461 };
462 let bci = fee_result.into_balance_change(id);
463 match bci.change() {
464 BalanceChange::NoBalanceChange => {}
465 other => panic!("Expected NoBalanceChange, got {:?}", other),
466 }
467 let refunds2 = fee_refunds_for_identity(id, 150);
469 let fee_result2 = FeeResult {
470 storage_fee: 100,
471 processing_fee: 50,
472 fee_refunds: refunds2,
473 removed_bytes_from_system: 0,
474 };
475 let bci2 = fee_result2.into_balance_change(id);
476 let result: Result<FeeResult, FeeError> = bci2.fee_result_outcome(0);
477 assert!(result.is_ok());
478 }
479
480 #[test]
483 fn into_balance_change_less_refund_than_fees() {
484 let id = make_id(1);
485 let refunds = fee_refunds_for_identity(id, 50);
487 let fee_result = FeeResult {
488 storage_fee: 100,
489 processing_fee: 50,
490 fee_refunds: refunds,
491 removed_bytes_from_system: 0,
492 };
493 let bci = fee_result.into_balance_change(id);
494 match bci.change() {
495 BalanceChange::RemoveFromBalance {
496 required_removed_balance,
497 desired_removed_balance,
498 } => {
499 assert_eq!(*required_removed_balance, 50);
501 assert_eq!(*desired_removed_balance, 100);
503 }
504 other => panic!("Expected RemoveFromBalance, got {:?}", other),
505 }
506 }
507
508 #[test]
509 fn into_balance_change_refund_equals_fees() {
510 let id = make_id(1);
511 let refunds = fee_refunds_for_identity(id, 150);
512 let fee_result = FeeResult {
513 storage_fee: 100,
514 processing_fee: 50,
515 fee_refunds: refunds,
516 removed_bytes_from_system: 0,
517 };
518 let bci = fee_result.into_balance_change(id);
519 assert_eq!(bci.change(), &BalanceChange::NoBalanceChange);
520 }
521
522 #[test]
523 fn into_balance_change_refund_greater_than_fees() {
524 let id = make_id(1);
525 let refunds = fee_refunds_for_identity(id, 300);
526 let fee_result = FeeResult {
527 storage_fee: 100,
528 processing_fee: 50,
529 fee_refunds: refunds,
530 removed_bytes_from_system: 0,
531 };
532 let bci = fee_result.into_balance_change(id);
533 match bci.change() {
534 BalanceChange::AddToBalance(amount) => {
535 assert_eq!(*amount, 150); }
537 other => panic!("Expected AddToBalance, got {:?}", other),
538 }
539 }
540
541 #[test]
542 fn into_balance_change_no_refunds_no_fees() {
543 let id = make_id(1);
544 let fee_result = FeeResult::default();
545 let bci = fee_result.into_balance_change(id);
546 assert_eq!(bci.change(), &BalanceChange::NoBalanceChange);
548 }
549
550 #[test]
551 fn into_balance_change_no_refunds_with_fees() {
552 let id = make_id(1);
553 let fee_result = FeeResult::default_with_fees(200, 100);
554 let bci = fee_result.into_balance_change(id);
555 match bci.change() {
556 BalanceChange::RemoveFromBalance {
557 required_removed_balance,
558 desired_removed_balance,
559 } => {
560 assert_eq!(*required_removed_balance, 200);
561 assert_eq!(*desired_removed_balance, 300);
562 }
563 other => panic!("Expected RemoveFromBalance, got {:?}", other),
564 }
565 }
566
567 #[test]
570 fn apply_user_fee_increase_zero_percent() {
571 let mut fr = FeeResult::default_with_fees(100, 1000);
572 fr.apply_user_fee_increase(0);
573 assert_eq!(fr.processing_fee, 1000);
574 }
575
576 #[test]
577 fn apply_user_fee_increase_100_percent() {
578 let mut fr = FeeResult::default_with_fees(100, 1000);
579 fr.apply_user_fee_increase(100);
580 assert_eq!(fr.processing_fee, 2000);
582 }
583
584 #[test]
585 fn apply_user_fee_increase_50_percent() {
586 let mut fr = FeeResult::default_with_fees(100, 1000);
587 fr.apply_user_fee_increase(50);
588 assert_eq!(fr.processing_fee, 1500);
590 }
591
592 #[test]
593 fn apply_user_fee_increase_does_not_affect_storage_fee() {
594 let mut fr = FeeResult::default_with_fees(500, 1000);
595 fr.apply_user_fee_increase(100);
596 assert_eq!(fr.storage_fee, 500);
597 assert_eq!(fr.processing_fee, 2000);
598 }
599
600 #[test]
601 fn apply_user_fee_increase_saturates_on_overflow() {
602 let mut fr = FeeResult::default_with_fees(0, u64::MAX);
603 fr.apply_user_fee_increase(100);
604 assert_eq!(fr.processing_fee, u64::MAX);
606 }
607
608 #[test]
609 fn apply_user_fee_increase_1_percent() {
610 let mut fr = FeeResult::default_with_fees(0, 10000);
611 fr.apply_user_fee_increase(1);
612 assert_eq!(fr.processing_fee, 10100);
614 }
615}