bee_ledger/types/
receipt.rs

1// Copyright 2020-2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// A type that wraps a receipt and the index of the milestone in which it was included.
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Receipt {
18    inner: ReceiptPayload,
19    included_in: MilestoneIndex,
20}
21
22impl Receipt {
23    /// Creates a new `Receipt`.
24    pub fn new(inner: ReceiptPayload, included_in: MilestoneIndex) -> Self {
25        Self { inner, included_in }
26    }
27
28    /// Returns the inner receipt of the `Receipt`.
29    pub fn inner(&self) -> &ReceiptPayload {
30        &self.inner
31    }
32
33    /// Returns the index of the milestone in which the `Receipt` was included.
34    pub fn included_in(&self) -> &MilestoneIndex {
35        &self.included_in
36    }
37
38    /// Semantically validates the `Receipt`.
39    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}