1use crate::amount::{self, Amount, Constraint, NegativeAllowed, NonNegative};
4
5use core::fmt;
6
7#[cfg(any(test, feature = "proptest-impl"))]
8use std::{borrow::Borrow, collections::HashMap};
9
10#[cfg(any(test, feature = "proptest-impl"))]
11use crate::{amount::MAX_MONEY, transaction::Transaction, transparent};
12
13#[cfg(any(test, feature = "proptest-impl"))]
14mod arbitrary;
15
16#[cfg(test)]
17mod tests;
18
19use ValueBalanceError::*;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
23pub struct ValueBalance<C> {
24 transparent: Amount<C>,
25 sprout: Amount<C>,
26 sapling: Amount<C>,
27 orchard: Amount<C>,
28 deferred: Amount<C>,
29}
30
31impl<C> ValueBalance<C>
32where
33 C: Constraint + Copy,
34{
35 pub fn from_transparent_amount(transparent_amount: Amount<C>) -> Self {
37 ValueBalance {
38 transparent: transparent_amount,
39 ..ValueBalance::zero()
40 }
41 }
42
43 pub fn from_sprout_amount(sprout_amount: Amount<C>) -> Self {
45 ValueBalance {
46 sprout: sprout_amount,
47 ..ValueBalance::zero()
48 }
49 }
50
51 pub fn from_sapling_amount(sapling_amount: Amount<C>) -> Self {
53 ValueBalance {
54 sapling: sapling_amount,
55 ..ValueBalance::zero()
56 }
57 }
58
59 pub fn from_orchard_amount(orchard_amount: Amount<C>) -> Self {
61 ValueBalance {
62 orchard: orchard_amount,
63 ..ValueBalance::zero()
64 }
65 }
66
67 pub fn transparent_amount(&self) -> Amount<C> {
69 self.transparent
70 }
71
72 pub fn set_transparent_value_balance(
75 &mut self,
76 transparent_value_balance: ValueBalance<C>,
77 ) -> &Self {
78 self.transparent = transparent_value_balance.transparent;
79 self
80 }
81
82 pub fn sprout_amount(&self) -> Amount<C> {
84 self.sprout
85 }
86
87 pub fn set_sprout_value_balance(&mut self, sprout_value_balance: ValueBalance<C>) -> &Self {
90 self.sprout = sprout_value_balance.sprout;
91 self
92 }
93
94 pub fn sapling_amount(&self) -> Amount<C> {
96 self.sapling
97 }
98
99 pub fn set_sapling_value_balance(&mut self, sapling_value_balance: ValueBalance<C>) -> &Self {
102 self.sapling = sapling_value_balance.sapling;
103 self
104 }
105
106 pub fn orchard_amount(&self) -> Amount<C> {
108 self.orchard
109 }
110
111 pub fn set_orchard_value_balance(&mut self, orchard_value_balance: ValueBalance<C>) -> &Self {
114 self.orchard = orchard_value_balance.orchard;
115 self
116 }
117
118 pub fn deferred_amount(&self) -> Amount<C> {
120 self.deferred
121 }
122
123 pub fn set_deferred_amount(&mut self, deferred_amount: Amount<C>) -> &Self {
125 self.deferred = deferred_amount;
126 self
127 }
128
129 pub fn zero() -> Self {
131 let zero = Amount::zero();
132 Self {
133 transparent: zero,
134 sprout: zero,
135 sapling: zero,
136 orchard: zero,
137 deferred: zero,
138 }
139 }
140
141 pub fn constrain<C2>(self) -> Result<ValueBalance<C2>, ValueBalanceError>
144 where
145 C2: Constraint,
146 {
147 Ok(ValueBalance::<C2> {
148 transparent: self.transparent.constrain().map_err(Transparent)?,
149 sprout: self.sprout.constrain().map_err(Sprout)?,
150 sapling: self.sapling.constrain().map_err(Sapling)?,
151 orchard: self.orchard.constrain().map_err(Orchard)?,
152 deferred: self.deferred.constrain().map_err(Deferred)?,
153 })
154 }
155}
156
157impl ValueBalance<NegativeAllowed> {
158 pub fn remaining_transaction_value(&self) -> Result<Amount<NonNegative>, amount::Error> {
171 (self.transparent + self.sprout + self.sapling + self.orchard)?.constrain::<NonNegative>()
177 }
178}
179
180impl ValueBalance<NonNegative> {
181 #[cfg(any(test, feature = "proptest-impl"))]
203 pub fn add_transaction(
204 self,
205 transaction: impl Borrow<Transaction>,
206 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
207 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
208 use std::ops::Neg;
209
210 let chain_value_pool_change = transaction
213 .borrow()
214 .value_balance_from_outputs(utxos)?
215 .neg();
216
217 self.add_chain_value_pool_change(chain_value_pool_change)
218 }
219
220 #[cfg(any(test, feature = "proptest-impl"))]
228 pub fn add_transparent_input(
229 self,
230 input: impl Borrow<transparent::Input>,
231 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
232 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
233 use std::ops::Neg;
234
235 let transparent_value_pool_change = input.borrow().value_from_outputs(utxos).neg();
238 let transparent_value_pool_change =
239 ValueBalance::from_transparent_amount(transparent_value_pool_change);
240
241 self.add_chain_value_pool_change(transparent_value_pool_change)
242 }
243
244 #[allow(clippy::unwrap_in_result)]
285 pub fn add_chain_value_pool_change(
286 self,
287 chain_value_pool_change: ValueBalance<NegativeAllowed>,
288 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
289 let mut chain_value_pool = self
290 .constrain::<NegativeAllowed>()
291 .expect("conversion from NonNegative to NegativeAllowed is always valid");
292 chain_value_pool = (chain_value_pool + chain_value_pool_change)?;
293
294 chain_value_pool.constrain()
295 }
296
297 #[cfg(any(test, feature = "proptest-impl"))]
301 pub fn fake_populated_pool() -> ValueBalance<NonNegative> {
302 let mut fake_value_pool = ValueBalance::zero();
303
304 let fake_transparent_value_balance =
305 ValueBalance::from_transparent_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
306 let fake_sprout_value_balance =
307 ValueBalance::from_sprout_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
308 let fake_sapling_value_balance =
309 ValueBalance::from_sapling_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
310 let fake_orchard_value_balance =
311 ValueBalance::from_orchard_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
312
313 fake_value_pool.set_transparent_value_balance(fake_transparent_value_balance);
314 fake_value_pool.set_sprout_value_balance(fake_sprout_value_balance);
315 fake_value_pool.set_sapling_value_balance(fake_sapling_value_balance);
316 fake_value_pool.set_orchard_value_balance(fake_orchard_value_balance);
317
318 fake_value_pool
319 }
320
321 pub fn to_bytes(self) -> [u8; 40] {
323 match [
324 self.transparent.to_bytes(),
325 self.sprout.to_bytes(),
326 self.sapling.to_bytes(),
327 self.orchard.to_bytes(),
328 self.deferred.to_bytes(),
329 ]
330 .concat()
331 .try_into()
332 {
333 Ok(bytes) => bytes,
334 _ => unreachable!(
335 "five [u8; 8] should always concat with no error into a single [u8; 40]"
336 ),
337 }
338 }
339
340 #[allow(clippy::unwrap_in_result)]
342 pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
343 let bytes_length = bytes.len();
344
345 match bytes_length {
347 32 | 40 => {}
348 _ => return Err(Unparsable),
349 };
350
351 let transparent = Amount::from_bytes(
352 bytes[0..8]
353 .try_into()
354 .expect("transparent amount should be parsable"),
355 )
356 .map_err(Transparent)?;
357
358 let sprout = Amount::from_bytes(
359 bytes[8..16]
360 .try_into()
361 .expect("sprout amount should be parsable"),
362 )
363 .map_err(Sprout)?;
364
365 let sapling = Amount::from_bytes(
366 bytes[16..24]
367 .try_into()
368 .expect("sapling amount should be parsable"),
369 )
370 .map_err(Sapling)?;
371
372 let orchard = Amount::from_bytes(
373 bytes[24..32]
374 .try_into()
375 .expect("orchard amount should be parsable"),
376 )
377 .map_err(Orchard)?;
378
379 let deferred = match bytes_length {
380 32 => Amount::zero(),
381 40 => Amount::from_bytes(
382 bytes[32..40]
383 .try_into()
384 .expect("deferred amount should be parsable"),
385 )
386 .map_err(Deferred)?,
387 _ => return Err(Unparsable),
388 };
389
390 Ok(ValueBalance {
391 transparent,
392 sprout,
393 sapling,
394 orchard,
395 deferred,
396 })
397 }
398}
399
400#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
401pub enum ValueBalanceError {
403 Transparent(amount::Error),
405
406 Sprout(amount::Error),
408
409 Sapling(amount::Error),
411
412 Orchard(amount::Error),
414
415 Deferred(amount::Error),
417
418 Unparsable,
420}
421
422impl fmt::Display for ValueBalanceError {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 f.write_str(&match self {
425 Transparent(e) => format!("transparent amount err: {e}"),
426 Sprout(e) => format!("sprout amount err: {e}"),
427 Sapling(e) => format!("sapling amount err: {e}"),
428 Orchard(e) => format!("orchard amount err: {e}"),
429 Deferred(e) => format!("deferred amount err: {e}"),
430 Unparsable => "value balance is unparsable".to_string(),
431 })
432 }
433}
434
435impl<C> std::ops::Add for ValueBalance<C>
436where
437 C: Constraint,
438{
439 type Output = Result<ValueBalance<C>, ValueBalanceError>;
440 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
441 Ok(ValueBalance::<C> {
442 transparent: (self.transparent + rhs.transparent).map_err(Transparent)?,
443 sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
444 sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
445 orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
446 deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
447 })
448 }
449}
450
451impl<C> std::ops::Add<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
452where
453 C: Constraint,
454{
455 type Output = Result<ValueBalance<C>, ValueBalanceError>;
456 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
457 self? + rhs
458 }
459}
460
461impl<C> std::ops::Add<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
462where
463 C: Constraint,
464{
465 type Output = Result<ValueBalance<C>, ValueBalanceError>;
466
467 fn add(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
468 self + rhs?
469 }
470}
471
472impl<C> std::ops::AddAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
473where
474 ValueBalance<C>: Copy,
475 C: Constraint,
476{
477 fn add_assign(&mut self, rhs: ValueBalance<C>) {
478 if let Ok(lhs) = *self {
479 *self = lhs + rhs;
480 }
481 }
482}
483
484impl<C> std::ops::Sub for ValueBalance<C>
485where
486 C: Constraint,
487{
488 type Output = Result<ValueBalance<C>, ValueBalanceError>;
489 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
490 Ok(ValueBalance::<C> {
491 transparent: (self.transparent - rhs.transparent).map_err(Transparent)?,
492 sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
493 sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
494 orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
495 deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
496 })
497 }
498}
499impl<C> std::ops::Sub<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
500where
501 C: Constraint,
502{
503 type Output = Result<ValueBalance<C>, ValueBalanceError>;
504 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
505 self? - rhs
506 }
507}
508
509impl<C> std::ops::Sub<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
510where
511 C: Constraint,
512{
513 type Output = Result<ValueBalance<C>, ValueBalanceError>;
514
515 fn sub(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
516 self - rhs?
517 }
518}
519
520impl<C> std::ops::SubAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
521where
522 ValueBalance<C>: Copy,
523 C: Constraint,
524{
525 fn sub_assign(&mut self, rhs: ValueBalance<C>) {
526 if let Ok(lhs) = *self {
527 *self = lhs - rhs;
528 }
529 }
530}
531
532impl<C> std::iter::Sum<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
533where
534 C: Constraint + Copy,
535{
536 fn sum<I: Iterator<Item = ValueBalance<C>>>(mut iter: I) -> Self {
537 iter.try_fold(ValueBalance::zero(), |acc, value_balance| {
538 acc + value_balance
539 })
540 }
541}
542
543impl<'amt, C> std::iter::Sum<&'amt ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
544where
545 C: Constraint + std::marker::Copy + 'amt,
546{
547 fn sum<I: Iterator<Item = &'amt ValueBalance<C>>>(iter: I) -> Self {
548 iter.copied().sum()
549 }
550}
551
552impl<C> std::ops::Neg for ValueBalance<C>
553where
554 C: Constraint,
555{
556 type Output = ValueBalance<NegativeAllowed>;
557
558 fn neg(self) -> Self::Output {
559 ValueBalance::<NegativeAllowed> {
560 transparent: self.transparent.neg(),
561 sprout: self.sprout.neg(),
562 sapling: self.sapling.neg(),
563 orchard: self.orchard.neg(),
564 deferred: self.deferred.neg(),
565 }
566 }
567}