masp_primitives/transaction/components/transparent/
builder.rs

1//! Types and functions for building transparent transaction components.
2
3use std::fmt;
4
5use crate::{
6    asset_type::AssetType,
7    transaction::{
8        TransparentAddress,
9        components::{
10            amount::{I128Sum, MAX_MONEY, ValueSum},
11            transparent::{self, Authorization, Authorized, Bundle, TxIn, TxOut, fees},
12        },
13        sighash::TransparentAuthorizingContext,
14    },
15};
16use borsh::BorshSchema;
17use borsh::{BorshDeserialize, BorshSerialize};
18
19#[derive(Debug, PartialEq, Eq)]
20pub enum Error {
21    InvalidAddress,
22    InvalidAmount,
23    InvalidAsset,
24}
25
26impl fmt::Display for Error {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        match self {
29            Error::InvalidAddress => write!(f, "Invalid address"),
30            Error::InvalidAmount => write!(f, "Invalid amount"),
31            Error::InvalidAsset => write!(f, "Invalid asset"),
32        }
33    }
34}
35
36/// An uninhabited type that allows the type of [`TransparentBuilder::inputs`]
37/// to resolve when the transparent-inputs feature is not turned on.
38#[cfg(not(feature = "transparent-inputs"))]
39enum InvalidTransparentInput {}
40
41#[cfg(not(feature = "transparent-inputs"))]
42impl fees::InputView for InvalidTransparentInput {
43    fn coin(&self) -> &TxOut {
44        panic!("transparent-inputs feature flag is not enabled.");
45    }
46}
47
48#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
49#[cfg(feature = "transparent-inputs")]
50#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)]
51struct TransparentInputInfo {
52    coin: TxOut,
53}
54
55#[cfg(feature = "transparent-inputs")]
56impl fees::InputView for TransparentInputInfo {
57    fn coin(&self) -> &TxOut {
58        &self.coin
59    }
60}
61
62#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)]
63pub struct TransparentBuilder {
64    #[cfg(feature = "transparent-inputs")]
65    inputs: Vec<TransparentInputInfo>,
66    vout: Vec<TxOut>,
67}
68
69#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
70#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
71pub struct Unauthorized {
72    #[cfg(feature = "transparent-inputs")]
73    inputs: Vec<TransparentInputInfo>,
74}
75
76impl Authorization for Unauthorized {
77    type TransparentSig = ();
78}
79
80impl TransparentBuilder {
81    /// Constructs a new TransparentBuilder
82    pub fn empty() -> Self {
83        TransparentBuilder {
84            #[cfg(feature = "transparent-inputs")]
85            inputs: vec![],
86            vout: vec![],
87        }
88    }
89
90    /// Returns the list of transparent inputs that will be consumed by the transaction being
91    /// constructed.
92    pub fn inputs(&self) -> &[impl fees::InputView] {
93        #[cfg(feature = "transparent-inputs")]
94        return &self.inputs;
95
96        #[cfg(not(feature = "transparent-inputs"))]
97        {
98            let invalid: &[InvalidTransparentInput] = &[];
99            return invalid;
100        }
101    }
102
103    /// Returns the transparent outputs that will be produced by the transaction being constructed.
104    pub fn outputs(&self) -> &[impl fees::OutputView] {
105        &self.vout
106    }
107
108    /// Adds a coin (the output of a previous transaction) to be spent to the transaction.
109    #[cfg(feature = "transparent-inputs")]
110    pub fn add_input(&mut self, coin: TxOut) -> Result<(), Error> {
111        self.inputs.push(TransparentInputInfo { coin });
112
113        Ok(())
114    }
115
116    pub fn add_output(
117        &mut self,
118        to: &TransparentAddress,
119        asset_type: AssetType,
120        value: u64,
121    ) -> Result<(), Error> {
122        if value > MAX_MONEY {
123            return Err(Error::InvalidAmount);
124        }
125
126        self.vout.push(TxOut {
127            asset_type,
128            value,
129            address: *to,
130        });
131
132        Ok(())
133    }
134
135    pub fn value_balance(&self) -> I128Sum {
136        #[cfg(feature = "transparent-inputs")]
137        let input_sum = self
138            .inputs
139            .iter()
140            .map(|input| ValueSum::from_pair(input.coin.asset_type, input.coin.value as i128))
141            .sum::<I128Sum>();
142
143        #[cfg(not(feature = "transparent-inputs"))]
144        let input_sum = ValueSum::zero();
145
146        let output_sum = self
147            .vout
148            .iter()
149            .map(|vo| ValueSum::from_pair(vo.asset_type, vo.value as i128))
150            .sum::<I128Sum>();
151
152        // Cannot panic when subtracting two positive i64
153        input_sum - output_sum
154    }
155
156    pub fn build(self) -> Option<transparent::Bundle<Unauthorized>> {
157        #[cfg(feature = "transparent-inputs")]
158        let vin: Vec<TxIn<Unauthorized>> = self
159            .inputs
160            .iter()
161            .map(|i| TxIn::<Unauthorized> {
162                asset_type: i.coin.asset_type,
163                value: i.coin.value,
164                address: i.coin.address,
165                transparent_sig: (),
166            })
167            .collect();
168
169        #[cfg(not(feature = "transparent-inputs"))]
170        let vin: Vec<TxIn> = vec![];
171
172        if vin.is_empty() && self.vout.is_empty() {
173            None
174        } else {
175            Some(transparent::Bundle {
176                vin,
177                vout: self.vout,
178                authorization: Unauthorized {
179                    #[cfg(feature = "transparent-inputs")]
180                    inputs: self.inputs,
181                },
182            })
183        }
184    }
185}
186
187#[cfg(not(feature = "transparent-inputs"))]
188impl TransparentAuthorizingContext for Unauthorized {
189    fn input_amounts(&self) -> Vec<(AssetType, i64)> {
190        vec![]
191    }
192}
193
194#[cfg(feature = "transparent-inputs")]
195impl TransparentAuthorizingContext for Unauthorized {
196    fn input_amounts(&self) -> Vec<(AssetType, u64)> {
197        self.inputs
198            .iter()
199            .map(|txin| (txin.coin.asset_type, txin.coin.value))
200            .collect()
201    }
202}
203
204impl Bundle<Unauthorized> {
205    pub fn apply_signatures(self) -> Bundle<Authorized> {
206        transparent::Bundle {
207            vin: self
208                .vin
209                .iter()
210                .map(|txin| TxIn {
211                    asset_type: txin.asset_type,
212                    address: txin.address,
213                    value: txin.value,
214                    transparent_sig: (),
215                })
216                .collect(),
217            vout: self.vout,
218            authorization: Authorized,
219        }
220    }
221}