1use super::FeeReserveFinalizationSummary;
2use crate::internal_prelude::*;
3use crate::{
4 errors::CanBeAbortion,
5 transaction::{AbortReason, CostingParameters},
6};
7use radix_engine_interface::blueprints::resource::LiquidFungibleResource;
8use radix_transactions::model::TipSpecifier;
9use radix_transactions::prelude::TransactionCostingParameters;
10use sbor::rust::cmp::min;
11
12#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
13pub enum FeeReserveError {
14 InsufficientBalance {
15 required: Decimal,
16 remaining: Decimal,
17 },
18 Overflow,
19 LimitExceeded {
20 limit: u32,
21 committed: u32,
22 new: u32,
23 },
24 LoanRepaymentFailed {
25 xrd_owed: Decimal,
26 },
27 Abort(AbortReason),
28}
29
30#[derive(Copy, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, ScryptoSbor)]
31pub enum StorageType {
32 State,
33 Archive,
34}
35
36impl CanBeAbortion for FeeReserveError {
37 fn abortion(&self) -> Option<&AbortReason> {
38 match self {
39 Self::Abort(reason) => Some(reason),
40 _ => None,
41 }
42 }
43}
44
45pub trait PreExecutionFeeReserve {
49 fn consume_deferred_execution(&mut self, cost_units: u32) -> Result<(), FeeReserveError>;
50
51 fn consume_deferred_finalization(&mut self, cost_units: u32) -> Result<(), FeeReserveError>;
52
53 fn consume_deferred_storage(
54 &mut self,
55 storage_type: StorageType,
56 size_increase: usize,
57 ) -> Result<(), FeeReserveError>;
58}
59
60pub trait ExecutionFeeReserve {
61 fn consume_execution(&mut self, cost_units: u32) -> Result<(), FeeReserveError>;
62
63 fn consume_finalization(&mut self, cost_units: u32) -> Result<(), FeeReserveError>;
64
65 fn consume_storage(
66 &mut self,
67 storage_type: StorageType,
68 size_increase: usize,
69 ) -> Result<(), FeeReserveError>;
70
71 fn consume_royalty(
72 &mut self,
73 royalty_amount: RoyaltyAmount,
74 recipient: RoyaltyRecipient,
75 ) -> Result<(), FeeReserveError>;
76
77 fn lock_fee(&mut self, vault_id: NodeId, fee: LiquidFungibleResource, contingent: bool);
78}
79
80pub trait FinalizingFeeReserve {
81 fn finalize(
82 self,
83 ) -> (
84 FeeReserveFinalizationSummary,
85 CostingParameters,
86 TransactionCostingParameters,
87 );
88}
89
90pub trait FeeReserve: PreExecutionFeeReserve + ExecutionFeeReserve + FinalizingFeeReserve {}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, ScryptoSbor)]
93pub enum RoyaltyRecipient {
94 Package(PackageAddress, NodeId),
95 Component(ComponentAddress, NodeId),
96}
97
98impl RoyaltyRecipient {
99 pub fn vault_id(&self) -> NodeId {
100 match self {
101 RoyaltyRecipient::Package(_, v) | RoyaltyRecipient::Component(_, v) => *v,
102 }
103 }
104}
105
106#[derive(Debug, Clone, ScryptoSbor)]
107pub struct SystemLoanFeeReserve {
108 costing_parameters: CostingParameters,
109 transaction_costing_parameters: TransactionCostingParameters,
110 abort_when_loan_repaid: bool,
111
112 effective_execution_cost_unit_price: Decimal,
114 effective_finalization_cost_unit_price: Decimal,
116
117 xrd_balance: Decimal,
119 xrd_owed: Decimal,
121
122 execution_cost_units_committed: u32,
124 execution_cost_units_deferred: u32,
125
126 finalization_cost_units_committed: u32,
128 finalization_cost_units_deferred: u32,
129
130 royalty_cost_committed: Decimal,
132 royalty_cost_breakdown: IndexMap<RoyaltyRecipient, Decimal>,
133
134 storage_cost_committed: Decimal,
136 storage_cost_deferred: IndexMap<StorageType, usize>,
137
138 locked_fees: Vec<(NodeId, LiquidFungibleResource, bool)>,
140}
141
142impl Default for SystemLoanFeeReserve {
143 fn default() -> Self {
144 Self::new(
145 CostingParameters::babylon_genesis(),
146 TransactionCostingParameters::default(),
147 false,
148 )
149 }
150}
151
152#[inline]
153fn checked_add(a: u32, b: u32) -> Result<u32, FeeReserveError> {
154 a.checked_add(b).ok_or(FeeReserveError::Overflow)
155}
156
157#[inline]
158fn checked_add_assign(value: &mut u32, summand: u32) -> Result<(), FeeReserveError> {
159 *value = checked_add(*value, summand)?;
160 Ok(())
161}
162
163impl SystemLoanFeeReserve {
164 pub fn new(
165 costing_parameters: CostingParameters,
166 transaction_costing_parameters: TransactionCostingParameters,
167 abort_when_loan_repaid: bool,
168 ) -> Self {
169 assert!(!costing_parameters.execution_cost_unit_price.is_negative());
171 assert!(!costing_parameters
172 .finalization_cost_unit_price
173 .is_negative());
174 assert!(!costing_parameters.usd_price.is_negative());
175 assert!(!costing_parameters.state_storage_price.is_negative());
176 assert!(!costing_parameters.archive_storage_price.is_negative());
177 assert!(!transaction_costing_parameters
178 .free_credit_in_xrd
179 .is_negative());
180
181 let tip_multiplier = transaction_costing_parameters.tip.fee_multiplier();
182
183 let effective_execution_cost_unit_price = costing_parameters
184 .execution_cost_unit_price
185 .checked_mul(tip_multiplier)
186 .unwrap();
187
188 let effective_finalization_cost_unit_price = costing_parameters
189 .finalization_cost_unit_price
190 .checked_mul(tip_multiplier)
191 .unwrap();
192
193 let system_loan_in_xrd = effective_execution_cost_unit_price
194 .checked_mul(costing_parameters.execution_cost_unit_loan)
195 .unwrap();
196
197 let starting_xrd_balance = system_loan_in_xrd
198 .checked_add(transaction_costing_parameters.free_credit_in_xrd)
199 .expect("Invalid system loan or free credit amount");
200
201 Self {
202 costing_parameters,
203 transaction_costing_parameters,
204 abort_when_loan_repaid,
205
206 effective_execution_cost_unit_price,
208 effective_finalization_cost_unit_price,
209
210 xrd_balance: starting_xrd_balance,
212 xrd_owed: system_loan_in_xrd,
213
214 execution_cost_units_committed: 0,
216 execution_cost_units_deferred: 0,
217
218 finalization_cost_units_committed: 0,
219 finalization_cost_units_deferred: 0,
220
221 royalty_cost_breakdown: index_map_new(),
222 royalty_cost_committed: Decimal::ZERO,
223
224 storage_cost_committed: Decimal::ZERO,
225 storage_cost_deferred: index_map_new(),
226
227 locked_fees: Vec::new(),
228 }
229 }
230
231 pub fn costing_parameters(&self) -> &CostingParameters {
232 &self.costing_parameters
233 }
234
235 pub fn transaction_costing_parameters(&self) -> &TransactionCostingParameters {
236 &self.transaction_costing_parameters
237 }
238
239 pub fn execution_cost_unit_limit(&self) -> u32 {
240 self.costing_parameters.execution_cost_unit_limit
241 }
242
243 pub fn execution_cost_unit_price(&self) -> Decimal {
244 self.costing_parameters.execution_cost_unit_price
245 }
246
247 pub fn finalization_cost_unit_limit(&self) -> u32 {
248 self.costing_parameters.finalization_cost_unit_limit
249 }
250
251 pub fn finalization_cost_unit_price(&self) -> Decimal {
252 self.costing_parameters.finalization_cost_unit_price
253 }
254
255 pub fn usd_price(&self) -> Decimal {
256 self.costing_parameters.usd_price
257 }
258
259 pub fn tip(&self) -> TipSpecifier {
260 self.transaction_costing_parameters.tip
261 }
262
263 pub fn fee_balance(&self) -> Decimal {
264 self.xrd_balance
265 }
266
267 pub fn royalty_cost_breakdown(&self) -> &IndexMap<RoyaltyRecipient, Decimal> {
268 &self.royalty_cost_breakdown
269 }
270
271 fn check_execution_cost_unit_limit(&self, cost_units: u32) -> Result<(), FeeReserveError> {
272 if checked_add(self.execution_cost_units_committed, cost_units)?
273 > self.costing_parameters.execution_cost_unit_limit
274 {
275 return Err(FeeReserveError::LimitExceeded {
276 limit: self.costing_parameters.execution_cost_unit_limit,
277 committed: self.execution_cost_units_committed,
278 new: cost_units,
279 });
280 }
281 Ok(())
282 }
283
284 fn check_finalization_cost_unit_limit(&self, cost_units: u32) -> Result<(), FeeReserveError> {
285 if checked_add(self.finalization_cost_units_committed, cost_units)?
286 > self.costing_parameters.finalization_cost_unit_limit
287 {
288 return Err(FeeReserveError::LimitExceeded {
289 limit: self.costing_parameters.finalization_cost_unit_limit,
290 committed: self.finalization_cost_units_committed,
291 new: cost_units,
292 });
293 }
294 Ok(())
295 }
296
297 fn consume_execution_internal(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
298 self.check_execution_cost_unit_limit(cost_units)?;
299
300 let amount = self
301 .effective_execution_cost_unit_price
302 .checked_mul(cost_units)
303 .ok_or(FeeReserveError::Overflow)?;
304 if self.xrd_balance < amount {
305 return Err(FeeReserveError::InsufficientBalance {
306 required: amount,
307 remaining: self.xrd_balance,
308 });
309 } else {
310 self.xrd_balance -= amount;
311 self.execution_cost_units_committed += cost_units;
312 Ok(())
313 }
314 }
315
316 fn consume_finalization_internal(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
317 self.check_finalization_cost_unit_limit(cost_units)?;
318
319 let amount = self
320 .effective_finalization_cost_unit_price
321 .checked_mul(cost_units)
322 .ok_or(FeeReserveError::Overflow)?;
323 if self.xrd_balance < amount {
324 return Err(FeeReserveError::InsufficientBalance {
325 required: amount,
326 remaining: self.xrd_balance,
327 });
328 } else {
329 self.xrd_balance -= amount;
330 self.finalization_cost_units_committed += cost_units;
331 Ok(())
332 }
333 }
334
335 fn consume_royalty_internal(
336 &mut self,
337 royalty_amount: RoyaltyAmount,
338 recipient: RoyaltyRecipient,
339 ) -> Result<(), FeeReserveError> {
340 let amount = match royalty_amount {
341 RoyaltyAmount::Xrd(xrd_amount) => xrd_amount,
342 RoyaltyAmount::Usd(usd_amount) => usd_amount
343 .checked_mul(self.costing_parameters.usd_price)
344 .ok_or(FeeReserveError::Overflow)?,
345 RoyaltyAmount::Free => Decimal::ZERO,
346 };
347
348 if self.xrd_balance < amount {
349 return Err(FeeReserveError::InsufficientBalance {
350 required: amount,
351 remaining: self.xrd_balance,
352 });
353 } else {
354 self.xrd_balance -= amount;
355 self.royalty_cost_breakdown
356 .entry(recipient)
357 .or_default()
358 .add_assign(amount);
359 self.royalty_cost_committed += amount;
360 Ok(())
361 }
362 }
363
364 pub fn repay_all(&mut self) -> Result<(), FeeReserveError> {
365 self.consume_execution_internal(self.execution_cost_units_deferred)?;
367 self.execution_cost_units_deferred = 0;
368
369 self.consume_finalization_internal(self.finalization_cost_units_deferred)?;
371 self.finalization_cost_units_deferred = 0;
372
373 let types: Vec<StorageType> = self.storage_cost_deferred.keys().cloned().collect();
375 for t in types {
376 self.consume_storage(t, self.storage_cost_deferred.get(&t).cloned().unwrap())?;
377 self.storage_cost_deferred.swap_remove(&t);
378 }
379
380 let amount = min(self.xrd_balance, self.xrd_owed);
382 self.xrd_owed -= amount;
383 self.xrd_balance -= amount; if !self.xrd_owed.is_zero() {
387 return Err(FeeReserveError::LoanRepaymentFailed {
388 xrd_owed: self.xrd_owed,
389 });
390 }
391
392 if self.abort_when_loan_repaid {
393 return Err(FeeReserveError::Abort(
394 AbortReason::ConfiguredAbortTriggeredOnFeeLoanRepayment,
395 ));
396 }
397
398 Ok(())
399 }
400
401 pub fn revert_royalty(&mut self) {
402 self.xrd_balance += self.royalty_cost_committed;
403 self.royalty_cost_breakdown.clear();
404 self.royalty_cost_committed = Decimal::ZERO;
405 }
406
407 #[inline]
408 pub fn fully_repaid(&self) -> bool {
409 self.xrd_owed == Decimal::ZERO
412 }
413}
414
415impl PreExecutionFeeReserve for SystemLoanFeeReserve {
416 fn consume_deferred_execution(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
417 checked_add_assign(&mut self.execution_cost_units_deferred, cost_units)?;
418
419 Ok(())
420 }
421
422 fn consume_deferred_finalization(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
423 checked_add_assign(&mut self.finalization_cost_units_deferred, cost_units)?;
424
425 Ok(())
426 }
427
428 fn consume_deferred_storage(
429 &mut self,
430 storage_type: StorageType,
431 size_increase: usize,
432 ) -> Result<(), FeeReserveError> {
433 self.storage_cost_deferred
434 .entry(storage_type)
435 .or_default()
436 .add_assign(size_increase);
437
438 Ok(())
439 }
440}
441
442impl ExecutionFeeReserve for SystemLoanFeeReserve {
443 fn consume_execution(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
444 if cost_units == 0 {
445 return Ok(());
446 }
447
448 self.consume_execution_internal(cost_units)?;
449
450 if !self.fully_repaid()
451 && self.execution_cost_units_committed
452 >= self.costing_parameters.execution_cost_unit_loan
453 {
454 self.repay_all()?;
455 }
456
457 Ok(())
458 }
459
460 fn consume_finalization(&mut self, cost_units: u32) -> Result<(), FeeReserveError> {
461 if cost_units == 0 {
462 return Ok(());
463 }
464
465 self.consume_finalization_internal(cost_units)?;
466
467 Ok(())
468 }
469
470 fn consume_royalty(
471 &mut self,
472 royalty_amount: RoyaltyAmount,
473 recipient: RoyaltyRecipient,
474 ) -> Result<(), FeeReserveError> {
475 if royalty_amount.is_zero() {
476 return Ok(());
477 }
478 if royalty_amount.is_negative() {
479 panic!("System invariant broken: Encountered negative royalty amount")
480 }
481
482 self.consume_royalty_internal(royalty_amount, recipient)?;
483
484 Ok(())
485 }
486
487 fn consume_storage(
488 &mut self,
489 storage_type: StorageType,
490 size_increase: usize,
491 ) -> Result<(), FeeReserveError> {
492 let amount = match storage_type {
493 StorageType::State => self.costing_parameters.state_storage_price,
494 StorageType::Archive => self.costing_parameters.archive_storage_price,
495 }
496 .checked_mul(size_increase)
497 .ok_or(FeeReserveError::Overflow)?;
498
499 if self.xrd_balance < amount {
500 return Err(FeeReserveError::InsufficientBalance {
501 required: amount,
502 remaining: self.xrd_balance,
503 });
504 } else {
505 self.xrd_balance -= amount;
506 self.storage_cost_committed += amount;
507 Ok(())
508 }
509 }
510
511 fn lock_fee(&mut self, vault_id: NodeId, mut fee: LiquidFungibleResource, contingent: bool) {
512 if !contingent {
514 self.xrd_balance = self
515 .xrd_balance
516 .checked_add(fee.amount())
517 .expect("No overflow due to limited XRD supply");
518 }
519
520 self.locked_fees
522 .push((vault_id, fee.take_all(), contingent));
523 }
524}
525
526impl FinalizingFeeReserve for SystemLoanFeeReserve {
527 fn finalize(
528 self,
529 ) -> (
530 FeeReserveFinalizationSummary,
531 CostingParameters,
532 TransactionCostingParameters,
533 ) {
534 let total_execution_cost_in_xrd = self
535 .costing_parameters
536 .execution_cost_unit_price
537 .checked_mul(Decimal::from(self.execution_cost_units_committed))
538 .unwrap();
539
540 let total_finalization_cost_in_xrd = self
541 .costing_parameters
542 .finalization_cost_unit_price
543 .checked_mul(Decimal::from(self.finalization_cost_units_committed))
544 .unwrap();
545
546 let tip_proportion = self.transaction_costing_parameters.tip.proportion();
547 let total_tipping_cost_in_xrd = total_execution_cost_in_xrd
548 .checked_mul(tip_proportion)
549 .unwrap()
550 .checked_add(
551 total_finalization_cost_in_xrd
552 .checked_mul(tip_proportion)
553 .unwrap(),
554 )
555 .unwrap();
556
557 let summary = FeeReserveFinalizationSummary {
558 total_execution_cost_units_consumed: self.execution_cost_units_committed,
559 total_finalization_cost_units_consumed: self.finalization_cost_units_committed,
560
561 total_execution_cost_in_xrd,
562 total_finalization_cost_in_xrd,
563 total_tipping_cost_in_xrd,
564 total_royalty_cost_in_xrd: self.royalty_cost_committed,
565 total_storage_cost_in_xrd: self.storage_cost_committed,
566 total_bad_debt_in_xrd: self.xrd_owed,
567 locked_fees: self.locked_fees,
568 royalty_cost_breakdown: self.royalty_cost_breakdown,
569 };
570
571 (
572 summary,
573 self.costing_parameters,
574 self.transaction_costing_parameters,
575 )
576 }
577}
578
579impl FeeReserve for SystemLoanFeeReserve {}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 const TEST_COMPONENT: ComponentAddress =
586 component_address(EntityType::GlobalGenericComponent, 5);
587 const TEST_VAULT_ID: NodeId = NodeId([0u8; NodeId::LENGTH]);
588 const TEST_VAULT_ID_2: NodeId = NodeId([1u8; NodeId::LENGTH]);
589
590 fn xrd<T: Into<Decimal>>(amount: T) -> LiquidFungibleResource {
591 LiquidFungibleResource::new(amount.into())
592 }
593
594 fn create_test_fee_reserve(
595 execution_cost_unit_price: Decimal,
596 usd_price: Decimal,
597 state_storage_price: Decimal,
598 tip_percentage: u16,
599 execution_cost_unit_limit: u32,
600 execution_cost_unit_loan: u32,
601 abort_when_loan_repaid: bool,
602 ) -> SystemLoanFeeReserve {
603 let mut costing_parameters = CostingParameters::babylon_genesis();
604 costing_parameters.execution_cost_unit_price = execution_cost_unit_price;
605 costing_parameters.execution_cost_unit_limit = execution_cost_unit_limit;
606 costing_parameters.execution_cost_unit_loan = execution_cost_unit_loan;
607 costing_parameters.usd_price = usd_price;
608 costing_parameters.state_storage_price = state_storage_price;
609 let mut transaction_costing_parameters = TransactionCostingParameters::default();
610 transaction_costing_parameters.tip = TipSpecifier::Percentage(tip_percentage);
611
612 SystemLoanFeeReserve::new(
613 costing_parameters,
614 transaction_costing_parameters,
615 abort_when_loan_repaid,
616 )
617 }
618
619 #[test]
620 fn test_consume_and_repay() {
621 let mut fee_reserve = create_test_fee_reserve(dec!(1), dec!(1), dec!(0), 2, 100, 5, false);
622 fee_reserve.consume_execution(2).unwrap();
623 fee_reserve.lock_fee(TEST_VAULT_ID, xrd(3), false);
624 fee_reserve.repay_all().unwrap();
625 let (summary, _, _) = fee_reserve.finalize();
626 assert_eq!(summary.loan_fully_repaid(), true);
627 assert_eq!(summary.total_execution_cost_units_consumed, 2);
628 assert_eq!(summary.total_execution_cost_in_xrd, dec!("2"));
629 assert_eq!(summary.total_tipping_cost_in_xrd, dec!("0.04"));
630 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("0"));
631 assert_eq!(summary.total_bad_debt_in_xrd, dec!("0"));
632 }
633
634 #[test]
635 fn test_out_of_cost_unit() {
636 let mut fee_reserve = create_test_fee_reserve(dec!(1), dec!(1), dec!(0), 2, 100, 5, false);
637 assert_eq!(
638 fee_reserve.consume_execution(6),
639 Err(FeeReserveError::InsufficientBalance {
640 required: dec!("6.12"),
641 remaining: dec!("5.1"),
642 }),
643 );
644 fee_reserve.repay_all().unwrap();
645 let (summary, _, _) = fee_reserve.finalize();
646 assert_eq!(summary.loan_fully_repaid(), true);
647 assert_eq!(summary.total_execution_cost_units_consumed, 0);
648 assert_eq!(summary.total_execution_cost_in_xrd, dec!("0"));
649 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("0"));
650 assert_eq!(summary.total_bad_debt_in_xrd, dec!("0"));
651 }
652
653 #[test]
654 fn test_lock_fee() {
655 let mut fee_reserve =
656 create_test_fee_reserve(dec!(1), dec!(1), dec!(0), 2, 100, 500, false);
657 fee_reserve.lock_fee(TEST_VAULT_ID, xrd(100), false);
658 fee_reserve.repay_all().unwrap();
659 let (summary, _, _) = fee_reserve.finalize();
660 assert_eq!(summary.loan_fully_repaid(), true);
661 assert_eq!(summary.total_execution_cost_units_consumed, 0);
662 assert_eq!(summary.total_execution_cost_in_xrd, dec!("0"));
663 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("0"));
664 assert_eq!(summary.total_bad_debt_in_xrd, dec!("0"));
665 }
666
667 #[test]
668 fn test_xrd_cost_unit_conversion() {
669 let mut fee_reserve =
670 create_test_fee_reserve(dec!(5), dec!(1), dec!(0), 0, 100, 500, false);
671 fee_reserve.lock_fee(TEST_VAULT_ID, xrd(100), false);
672 fee_reserve.repay_all().unwrap();
673 let (summary, _, _) = fee_reserve.finalize();
674 assert_eq!(summary.loan_fully_repaid(), true);
675 assert_eq!(summary.total_execution_cost_units_consumed, 0);
676 assert_eq!(summary.total_execution_cost_in_xrd, dec!("0"));
677 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("0"));
678 assert_eq!(summary.total_bad_debt_in_xrd, dec!("0"));
679 assert_eq!(summary.locked_fees, vec![(TEST_VAULT_ID, xrd(100), false)],);
680 }
681
682 #[test]
683 fn test_bad_debt() {
684 let mut fee_reserve = create_test_fee_reserve(dec!(5), dec!(1), dec!(0), 1, 100, 50, false);
685 fee_reserve.consume_execution(2).unwrap();
686 assert_eq!(
687 fee_reserve.repay_all(),
688 Err(FeeReserveError::LoanRepaymentFailed {
689 xrd_owed: dec!("10.1")
690 })
691 );
692 let (summary, _, _) = fee_reserve.finalize();
693 assert_eq!(summary.loan_fully_repaid(), false);
694 assert_eq!(summary.total_execution_cost_units_consumed, 2);
695 assert_eq!(summary.total_execution_cost_in_xrd, dec!("10"));
696 assert_eq!(summary.total_tipping_cost_in_xrd, dec!("0.1"));
697 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("0"));
698 assert_eq!(summary.total_bad_debt_in_xrd, dec!("10.1"));
699 assert_eq!(summary.locked_fees, vec![],);
700 }
701
702 #[test]
703 fn test_royalty_execution_mix() {
704 let mut fee_reserve = create_test_fee_reserve(dec!(5), dec!(2), dec!(0), 1, 100, 50, false);
705 fee_reserve.consume_execution(2).unwrap();
706 fee_reserve
707 .consume_royalty(
708 RoyaltyAmount::Xrd(2.into()),
709 RoyaltyRecipient::Package(PACKAGE_PACKAGE, TEST_VAULT_ID),
710 )
711 .unwrap();
712 fee_reserve
713 .consume_royalty(
714 RoyaltyAmount::Usd(7.into()),
715 RoyaltyRecipient::Package(PACKAGE_PACKAGE, TEST_VAULT_ID),
716 )
717 .unwrap();
718 fee_reserve.lock_fee(TEST_VAULT_ID, xrd(100), false);
719 fee_reserve.repay_all().unwrap();
720 let (summary, _, _) = fee_reserve.finalize();
721 assert_eq!(summary.loan_fully_repaid(), true);
722 assert_eq!(summary.total_execution_cost_in_xrd, dec!("10"));
723 assert_eq!(summary.total_tipping_cost_in_xrd, dec!("0.1"));
724 assert_eq!(summary.total_royalty_cost_in_xrd, dec!("16"));
725 assert_eq!(summary.total_bad_debt_in_xrd, dec!("0"));
726 assert_eq!(summary.locked_fees, vec![(TEST_VAULT_ID, xrd(100), false)]);
727 assert_eq!(summary.total_execution_cost_units_consumed, 2);
728 assert_eq!(
729 summary.royalty_cost_breakdown,
730 indexmap!(
731 RoyaltyRecipient::Package(PACKAGE_PACKAGE, TEST_VAULT_ID) => dec!("16")
732 )
733 );
734 }
735
736 #[test]
737 fn test_royalty_insufficient_balance() {
738 let mut fee_reserve =
739 create_test_fee_reserve(dec!(1), dec!(1), dec!(0), 0, 1000, 50, false);
740 fee_reserve.lock_fee(TEST_VAULT_ID, xrd(100), false);
741 fee_reserve
742 .consume_royalty(
743 RoyaltyAmount::Xrd(90.into()),
744 RoyaltyRecipient::Package(PACKAGE_PACKAGE, TEST_VAULT_ID),
745 )
746 .unwrap();
747 assert_eq!(
748 fee_reserve.consume_royalty(
749 RoyaltyAmount::Xrd(80.into()),
750 RoyaltyRecipient::Component(TEST_COMPONENT, TEST_VAULT_ID_2),
751 ),
752 Err(FeeReserveError::InsufficientBalance {
753 required: dec!("80"),
754 remaining: dec!("60"),
755 }),
756 );
757 }
758}