masp_primitives/transaction/components/
transparent.rs1use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4use std::fmt::{self, Debug};
5use std::io::{self, Read, Write};
6
7use crate::asset_type::AssetType;
8use crate::transaction::TransparentAddress;
9use crate::MaybeArbitrary;
10use borsh::schema::add_definition;
11use borsh::schema::Declaration;
12use borsh::schema::Definition;
13use borsh::schema::Fields;
14use std::collections::BTreeMap;
15
16use super::amount::{BalanceError, I128Sum, ValueSum, MAX_MONEY};
17
18pub mod builder;
19pub mod fees;
20
21pub trait Authorization: fmt::Debug {
22 type TransparentSig: fmt::Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>;
23}
24
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26#[derive(Debug, Copy, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
27pub struct Authorized;
28
29impl Authorization for Authorized {
30 type TransparentSig = ();
31}
32
33pub trait MapAuth<A: Authorization, B: Authorization> {
34 fn map_script_sig(&self, s: A::TransparentSig, pos: usize) -> B::TransparentSig;
35 fn map_authorization(&self, s: A) -> B;
36}
37
38impl MapAuth<Authorized, Authorized> for () {
45 fn map_script_sig(
46 &self,
47 s: <Authorized as Authorization>::TransparentSig,
48 _pos: usize,
49 ) -> <Authorized as Authorization>::TransparentSig {
50 s
51 }
52
53 fn map_authorization(&self, a: Authorized) -> Authorized {
54 a
55 }
56}
57
58#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
59#[derive(Debug, Clone, PartialEq)]
60pub struct Bundle<A: Authorization> {
61 pub vin: Vec<TxIn<A>>,
62 pub vout: Vec<TxOut>,
63 pub authorization: A,
64}
65
66impl<A: Authorization> Bundle<A> {
67 pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
68 Bundle {
69 vin: self
70 .vin
71 .into_iter()
72 .enumerate()
73 .map(|(pos, txin)| TxIn {
74 asset_type: txin.asset_type,
75 address: txin.address,
76 transparent_sig: f.map_script_sig(txin.transparent_sig, pos),
77 value: txin.value,
78 })
79 .collect(),
80 vout: self.vout,
81 authorization: f.map_authorization(self.authorization),
82 }
83 }
84
85 pub fn value_balance<E, F>(&self) -> I128Sum
91 where
92 E: From<BalanceError>,
93 {
94 let input_sum = self
95 .vin
96 .iter()
97 .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128))
98 .sum::<I128Sum>();
99
100 let output_sum = self
101 .vout
102 .iter()
103 .map(|p| ValueSum::from_pair(p.asset_type, p.value as i128))
104 .sum::<I128Sum>();
105
106 input_sum - output_sum
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
113pub struct TxIn<A: Authorization> {
114 pub asset_type: AssetType,
115 pub value: u64,
116 pub address: TransparentAddress,
117 pub transparent_sig: A::TransparentSig,
118}
119
120impl TxIn<Authorized> {
121 pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
122 let asset_type = AssetType::read(reader)?;
123 let value = {
124 let mut tmp = [0u8; 8];
125 reader.read_exact(&mut tmp)?;
126 u64::from_le_bytes(tmp)
127 };
128 if value > MAX_MONEY {
129 return Err(io::Error::new(
130 io::ErrorKind::InvalidData,
131 "value out of range",
132 ));
133 }
134 let address = {
135 let mut tmp = [0u8; 20];
136 reader.read_exact(&mut tmp)?;
137 TransparentAddress(tmp)
138 };
139
140 Ok(TxIn {
141 asset_type,
142 value,
143 address,
144 transparent_sig: (),
145 })
146 }
147
148 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
149 writer.write_all(self.asset_type.get_identifier())?;
150 writer.write_all(&self.value.to_le_bytes())?;
151 writer.write_all(&self.address.0)
152 }
153}
154
155impl BorshSerialize for TxIn<Authorized> {
156 fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
157 self.write(writer)
158 }
159}
160
161impl BorshDeserialize for TxIn<Authorized> {
162 fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
163 Self::read(reader)
164 }
165}
166
167impl BorshSchema for TxIn<Authorized> {
168 fn add_definitions_recursively(
169 definitions: &mut BTreeMap<borsh::schema::Declaration, borsh::schema::Definition>,
170 ) {
171 let definition = Definition::Struct {
172 fields: Fields::NamedFields(vec![
173 ("asset_type".into(), AssetType::declaration()),
174 ("value".into(), u64::declaration()),
175 ("address".into(), TransparentAddress::declaration()),
176 ]),
177 };
178 add_definition(Self::declaration(), definition, definitions);
179 AssetType::add_definitions_recursively(definitions);
180 u64::add_definitions_recursively(definitions);
181 TransparentAddress::add_definitions_recursively(definitions);
182 }
183
184 fn declaration() -> borsh::schema::Declaration {
185 "TxIn<Authorized>".into()
186 }
187}
188
189#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Ord, Eq)]
190#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
191pub struct TxOut {
192 pub asset_type: AssetType,
193 pub value: u64,
194 pub address: TransparentAddress,
195}
196
197impl TxOut {
198 pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
199 let asset_type = AssetType::read(reader)?;
200 let value = {
201 let mut tmp = [0u8; 8];
202 reader.read_exact(&mut tmp)?;
203 u64::from_le_bytes(tmp)
204 };
205 if value > MAX_MONEY {
206 return Err(io::Error::new(
207 io::ErrorKind::InvalidData,
208 "value out of range",
209 ));
210 }
211
212 let address = {
213 let mut tmp = [0u8; 20];
214 reader.read_exact(&mut tmp)?;
215 TransparentAddress(tmp)
216 };
217
218 Ok(TxOut {
219 asset_type,
220 value,
221 address,
222 })
223 }
224
225 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
226 writer.write_all(self.asset_type.get_identifier())?;
227 writer.write_all(&self.value.to_le_bytes())?;
228 writer.write_all(&self.address.0)
229 }
230
231 pub fn recipient_address(&self) -> TransparentAddress {
233 self.address
234 }
235}
236
237impl BorshDeserialize for TxOut {
238 fn deserialize_reader<R: Read>(reader: &mut R) -> io::Result<Self> {
239 Self::read(reader)
240 }
241}
242
243impl BorshSerialize for TxOut {
244 fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
245 self.write(writer)
246 }
247}
248
249impl BorshSchema for TxOut {
250 fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
251 let definition = Definition::Struct {
252 fields: Fields::NamedFields(vec![
253 ("asset_type".into(), AssetType::declaration()),
254 ("value".into(), u64::declaration()),
255 ("address".into(), TransparentAddress::declaration()),
256 ]),
257 };
258 add_definition(Self::declaration(), definition, definitions);
259 AssetType::add_definitions_recursively(definitions);
260 u64::add_definitions_recursively(definitions);
261 TransparentAddress::add_definitions_recursively(definitions);
262 }
263
264 fn declaration() -> Declaration {
265 "TxOut".into()
266 }
267}
268
269#[cfg(any(test, feature = "test-dependencies"))]
270pub mod testing {
271 use proptest::collection::vec;
272 use proptest::prelude::*;
273
274 use crate::transaction::components::amount::testing::arb_nonnegative_amount;
275 use crate::transaction::TransparentAddress;
276
277 use super::{Authorized, Bundle, TxIn, TxOut};
278
279 prop_compose! {
280 pub fn arb_transparent_address()(value in prop::array::uniform20(prop::num::u8::ANY)) -> TransparentAddress {
281 TransparentAddress(value)
282 }
283 }
284
285 prop_compose! {
286 pub fn arb_txin()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxIn<Authorized> {
287 let (asset_type, value) = amt.components().next().unwrap();
288 TxIn { asset_type: *asset_type, value: *value, address: addr, transparent_sig: () }
289 }
290 }
291
292 prop_compose! {
293 pub fn arb_txout()(amt in arb_nonnegative_amount(), addr in arb_transparent_address()) -> TxOut {
294 let (asset_type, value) = amt.components().next().unwrap();
295
296 TxOut { asset_type: *asset_type, value: *value, address : addr }
297 }
298 }
299
300 prop_compose! {
301 pub fn arb_bundle()(
302 vin in vec(arb_txin(), 0..10),
303 vout in vec(arb_txout(), 0..10),
304 ) -> Option<Bundle<Authorized>> {
305 if vout.is_empty() {
306 None
307 } else {
308 Some(Bundle {vin, vout, authorization: Authorized })
309 }
310 }
311 }
312}
313
314#[cfg(test)]
315mod test_serialization {
316 use super::*;
317
318 #[test]
320 fn test_roundtrip_txin() {
321 let asset_type = AssetType::new_with_nonce(&[1, 2, 3, 4], 1).expect("Test failed");
322 let txin = TxIn::<Authorized> {
323 asset_type,
324 value: MAX_MONEY - 1,
325 address: TransparentAddress([12u8; 20]),
326 transparent_sig: (),
327 };
328
329 let mut buf = vec![];
330 txin.write(&mut buf).expect("Test failed");
331 let deserialized = TxIn::read::<&[u8]>(&mut buf.as_ref()).expect("Test failed");
332 assert_eq!(deserialized, txin);
333 }
334
335 #[test]
337 fn test_roundtrip_txout() {
338 let asset_type = AssetType::new_with_nonce(&[1, 2, 3, 4], 1).expect("Test failed");
339 let txout = TxOut {
340 asset_type,
341 value: MAX_MONEY - 1,
342 address: TransparentAddress([12u8; 20]),
343 };
344
345 let mut buf = vec![];
346 txout.write(&mut buf).expect("Test failed");
347 let deserialized = TxOut::read::<&[u8]>(&mut buf.as_ref()).expect("Test failed");
348 assert_eq!(deserialized, txout);
349 }
350}