bee_ledger/types/
receipt.rs1use crate::types::{error::Error, TreasuryOutput};
5
6use bee_common::packable::{Packable, Read, Write};
7use bee_message::{
8 constants::IOTA_SUPPLY,
9 input::Input,
10 milestone::MilestoneIndex,
11 output::Output,
12 payload::{receipt::ReceiptPayload, Payload},
13};
14
15#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Receipt {
18 inner: ReceiptPayload,
19 included_in: MilestoneIndex,
20}
21
22impl Receipt {
23 pub fn new(inner: ReceiptPayload, included_in: MilestoneIndex) -> Self {
25 Self { inner, included_in }
26 }
27
28 pub fn inner(&self) -> &ReceiptPayload {
30 &self.inner
31 }
32
33 pub fn included_in(&self) -> &MilestoneIndex {
35 &self.included_in
36 }
37
38 pub fn validate(&self, consumed_treasury_output: &TreasuryOutput) -> Result<(), Error> {
40 let mut migrated_amount: u64 = 0;
41 let transaction = match self.inner().transaction() {
42 Payload::TreasuryTransaction(transaction) => transaction,
43 Payload::Indexation(_) | Payload::Milestone(_) | Payload::Receipt(_) | Payload::Transaction(_) => {
44 return Err(Error::UnsupportedPayloadKind(self.inner().transaction().kind()));
45 }
46 };
47
48 for funds in self.inner().funds() {
49 migrated_amount = migrated_amount.checked_add(funds.output().amount()).ok_or_else(|| {
50 Error::MigratedFundsAmountOverflow(migrated_amount as u128 + funds.output().amount() as u128)
51 })?;
52 }
53
54 if migrated_amount > IOTA_SUPPLY {
55 return Err(Error::InvalidMigratedFundsAmount(migrated_amount));
56 }
57
58 match transaction.input() {
59 Input::Treasury(input) => {
60 if input.milestone_id() != consumed_treasury_output.milestone_id() {
61 return Err(Error::ConsumedTreasuryOutputMismatch(
62 *input.milestone_id(),
63 *consumed_treasury_output.milestone_id(),
64 ));
65 }
66 }
67 Input::Utxo(_) => return Err(Error::UnsupportedInputKind(transaction.input().kind())),
68 };
69
70 let created_treasury_output = match transaction.output() {
71 Output::Treasury(output) => output,
72 Output::SignatureLockedDustAllowance(_) | Output::SignatureLockedSingle(_) => {
73 return Err(Error::UnsupportedOutputKind(transaction.output().kind()));
74 }
75 };
76
77 let created_amount = consumed_treasury_output
78 .inner()
79 .amount()
80 .checked_sub(migrated_amount)
81 .ok_or_else(|| {
82 Error::MigratedFundsAmountOverflow(
83 (consumed_treasury_output.inner().amount() as i128 - migrated_amount as i128) as u128,
84 )
85 })?;
86
87 if created_amount != created_treasury_output.amount() {
88 return Err(Error::TreasuryAmountMismatch(
89 created_amount,
90 created_treasury_output.amount(),
91 ));
92 }
93
94 Ok(())
95 }
96}
97
98impl Packable for Receipt {
99 type Error = Error;
100
101 fn packed_len(&self) -> usize {
102 self.inner.packed_len() + self.included_in.packed_len()
103 }
104
105 fn pack<W: Write>(&self, writer: &mut W) -> Result<(), Self::Error> {
106 self.inner.pack(writer)?;
107 self.included_in.pack(writer)?;
108
109 Ok(())
110 }
111
112 fn unpack_inner<R: Read + ?Sized, const CHECK: bool>(reader: &mut R) -> Result<Self, Self::Error> {
113 let inner = ReceiptPayload::unpack_inner::<R, CHECK>(reader)?;
114 let included_in = MilestoneIndex::unpack_inner::<R, CHECK>(reader)?;
115
116 Ok(Self::new(inner, included_in))
117 }
118}