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