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 ironwood: Amount<C>,
30}
31
32impl<C> ValueBalance<C>
33where
34 C: Constraint + Copy,
35{
36 pub fn from_transparent_amount(transparent_amount: Amount<C>) -> Self {
38 ValueBalance {
39 transparent: transparent_amount,
40 ..ValueBalance::zero()
41 }
42 }
43
44 pub fn from_sprout_amount(sprout_amount: Amount<C>) -> Self {
46 ValueBalance {
47 sprout: sprout_amount,
48 ..ValueBalance::zero()
49 }
50 }
51
52 pub fn from_sapling_amount(sapling_amount: Amount<C>) -> Self {
54 ValueBalance {
55 sapling: sapling_amount,
56 ..ValueBalance::zero()
57 }
58 }
59
60 pub fn from_orchard_amount(orchard_amount: Amount<C>) -> Self {
62 ValueBalance {
63 orchard: orchard_amount,
64 ..ValueBalance::zero()
65 }
66 }
67
68 pub fn from_ironwood_amount(ironwood_amount: Amount<C>) -> Self {
70 ValueBalance {
71 ironwood: ironwood_amount,
72 ..ValueBalance::zero()
73 }
74 }
75
76 pub fn transparent_amount(&self) -> Amount<C> {
78 self.transparent
79 }
80
81 pub fn set_transparent_value_balance(
84 &mut self,
85 transparent_value_balance: ValueBalance<C>,
86 ) -> &Self {
87 self.transparent = transparent_value_balance.transparent;
88 self
89 }
90
91 pub fn sprout_amount(&self) -> Amount<C> {
93 self.sprout
94 }
95
96 pub fn set_sprout_value_balance(&mut self, sprout_value_balance: ValueBalance<C>) -> &Self {
99 self.sprout = sprout_value_balance.sprout;
100 self
101 }
102
103 pub fn sapling_amount(&self) -> Amount<C> {
105 self.sapling
106 }
107
108 pub fn set_sapling_value_balance(&mut self, sapling_value_balance: ValueBalance<C>) -> &Self {
111 self.sapling = sapling_value_balance.sapling;
112 self
113 }
114
115 pub fn orchard_amount(&self) -> Amount<C> {
117 self.orchard
118 }
119
120 pub fn set_orchard_value_balance(&mut self, orchard_value_balance: ValueBalance<C>) -> &Self {
123 self.orchard = orchard_value_balance.orchard;
124 self
125 }
126
127 pub fn deferred_amount(&self) -> Amount<C> {
129 self.deferred
130 }
131
132 pub fn set_deferred_amount(&mut self, deferred_amount: Amount<C>) -> &Self {
134 self.deferred = deferred_amount;
135 self
136 }
137
138 pub fn ironwood_amount(&self) -> Amount<C> {
140 self.ironwood
141 }
142
143 pub fn set_ironwood_value_balance(&mut self, ironwood_value_balance: ValueBalance<C>) -> &Self {
146 self.ironwood = ironwood_value_balance.ironwood;
147 self
148 }
149
150 pub fn zero() -> Self {
152 let zero = Amount::zero();
153 Self {
154 transparent: zero,
155 sprout: zero,
156 sapling: zero,
157 orchard: zero,
158 deferred: zero,
159 ironwood: zero,
160 }
161 }
162
163 pub fn total(self) -> Result<Amount<C>, amount::Error> {
165 let total: i128 = [
166 self.transparent,
167 self.sprout,
168 self.sapling,
169 self.orchard,
170 self.deferred,
171 self.ironwood,
172 ]
173 .into_iter()
174 .map(|amount| i128::from(amount.zatoshis()))
175 .sum();
176
177 Amount::try_from(total)
178 }
179
180 pub fn constrain<C2>(self) -> Result<ValueBalance<C2>, ValueBalanceError>
183 where
184 C2: Constraint,
185 {
186 Ok(ValueBalance::<C2> {
187 transparent: self.transparent.constrain().map_err(Transparent)?,
188 sprout: self.sprout.constrain().map_err(Sprout)?,
189 sapling: self.sapling.constrain().map_err(Sapling)?,
190 orchard: self.orchard.constrain().map_err(Orchard)?,
191 deferred: self.deferred.constrain().map_err(Deferred)?,
192 ironwood: self.ironwood.constrain().map_err(Ironwood)?,
193 })
194 }
195}
196
197impl ValueBalance<NegativeAllowed> {
198 pub fn remaining_transaction_value(&self) -> Result<Amount<NonNegative>, amount::Error> {
211 (self.transparent + self.sprout + self.sapling + self.orchard + self.ironwood)?
220 .constrain::<NonNegative>()
221 }
222}
223
224impl ValueBalance<NonNegative> {
225 #[cfg(any(test, feature = "proptest-impl"))]
247 pub fn add_transaction(
248 self,
249 transaction: impl Borrow<Transaction>,
250 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
251 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
252 use std::ops::Neg;
253
254 let chain_value_pool_change = transaction
257 .borrow()
258 .value_balance_from_outputs(utxos)?
259 .neg();
260
261 self.add_chain_value_pool_change(chain_value_pool_change)
262 }
263
264 #[cfg(any(test, feature = "proptest-impl"))]
272 pub fn add_transparent_input(
273 self,
274 input: impl Borrow<transparent::Input>,
275 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
276 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
277 use std::ops::Neg;
278
279 let transparent_value_pool_change = input.borrow().value_from_outputs(utxos).neg();
282 let transparent_value_pool_change =
283 ValueBalance::from_transparent_amount(transparent_value_pool_change);
284
285 self.add_chain_value_pool_change(transparent_value_pool_change)
286 }
287
288 #[allow(clippy::unwrap_in_result)]
329 pub fn add_chain_value_pool_change(
330 self,
331 chain_value_pool_change: ValueBalance<NegativeAllowed>,
332 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
333 let mut chain_value_pool = self
334 .constrain::<NegativeAllowed>()
335 .expect("conversion from NonNegative to NegativeAllowed is always valid");
336 chain_value_pool = (chain_value_pool + chain_value_pool_change)?;
337
338 let chain_value_pool = chain_value_pool.constrain::<NonNegative>()?;
339
340 chain_value_pool.total().map_err(ValueBalanceError::Total)?;
343
344 Ok(chain_value_pool)
345 }
346
347 #[cfg(any(test, feature = "proptest-impl"))]
354 pub fn fake_populated_pool() -> ValueBalance<NonNegative> {
355 let mut fake_value_pool = ValueBalance::zero();
356
357 let fake_transparent_value_balance =
358 ValueBalance::from_transparent_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
359 let fake_sprout_value_balance =
360 ValueBalance::from_sprout_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
361 let fake_sapling_value_balance =
362 ValueBalance::from_sapling_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
363 let fake_orchard_value_balance =
364 ValueBalance::from_orchard_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
365 let fake_ironwood_value_balance =
366 ValueBalance::from_ironwood_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
367
368 fake_value_pool.set_transparent_value_balance(fake_transparent_value_balance);
369 fake_value_pool.set_sprout_value_balance(fake_sprout_value_balance);
370 fake_value_pool.set_sapling_value_balance(fake_sapling_value_balance);
371 fake_value_pool.set_orchard_value_balance(fake_orchard_value_balance);
372 fake_value_pool.set_ironwood_value_balance(fake_ironwood_value_balance);
373
374 fake_value_pool
375 }
376
377 pub fn to_bytes(self) -> [u8; 48] {
383 match [
384 self.transparent.to_bytes(),
385 self.sprout.to_bytes(),
386 self.sapling.to_bytes(),
387 self.orchard.to_bytes(),
388 self.deferred.to_bytes(),
389 self.ironwood.to_bytes(),
390 ]
391 .concat()
392 .try_into()
393 {
394 Ok(bytes) => bytes,
395 _ => unreachable!(
396 "six [u8; 8] should always concat with no error into a single [u8; 48]"
397 ),
398 }
399 }
400
401 #[allow(clippy::unwrap_in_result)]
406 pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
407 let bytes_length = bytes.len();
408
409 match bytes_length {
411 32 | 40 | 48 => {}
412 _ => return Err(Unparsable),
413 };
414
415 let transparent = Amount::from_bytes(
416 bytes[0..8]
417 .try_into()
418 .expect("transparent amount should be parsable"),
419 )
420 .map_err(Transparent)?;
421
422 let sprout = Amount::from_bytes(
423 bytes[8..16]
424 .try_into()
425 .expect("sprout amount should be parsable"),
426 )
427 .map_err(Sprout)?;
428
429 let sapling = Amount::from_bytes(
430 bytes[16..24]
431 .try_into()
432 .expect("sapling amount should be parsable"),
433 )
434 .map_err(Sapling)?;
435
436 let orchard = Amount::from_bytes(
437 bytes[24..32]
438 .try_into()
439 .expect("orchard amount should be parsable"),
440 )
441 .map_err(Orchard)?;
442
443 let deferred = match bytes_length {
444 32 => Amount::zero(),
445 40 | 48 => Amount::from_bytes(
446 bytes[32..40]
447 .try_into()
448 .expect("deferred amount should be parsable"),
449 )
450 .map_err(Deferred)?,
451 _ => return Err(Unparsable),
452 };
453
454 let ironwood = match bytes_length {
455 32 | 40 => Amount::zero(),
456 48 => Amount::from_bytes(
457 bytes[40..48]
458 .try_into()
459 .expect("ironwood amount should be parsable"),
460 )
461 .map_err(Ironwood)?,
462 _ => return Err(Unparsable),
463 };
464
465 Ok(ValueBalance {
466 transparent,
467 sprout,
468 sapling,
469 orchard,
470 deferred,
471 ironwood,
472 })
473 }
474}
475
476#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
477pub enum ValueBalanceError {
479 Transparent(amount::Error),
481
482 Sprout(amount::Error),
484
485 Sapling(amount::Error),
487
488 Orchard(amount::Error),
490
491 Deferred(amount::Error),
493
494 Ironwood(amount::Error),
496
497 Total(amount::Error),
499
500 Unparsable,
502}
503
504impl fmt::Display for ValueBalanceError {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 f.write_str(&match self {
507 Transparent(e) => format!("transparent amount err: {e}"),
508 Sprout(e) => format!("sprout amount err: {e}"),
509 Sapling(e) => format!("sapling amount err: {e}"),
510 Orchard(e) => format!("orchard amount err: {e}"),
511 Deferred(e) => format!("deferred amount err: {e}"),
512 Ironwood(e) => format!("ironwood amount err: {e}"),
513 Total(e) => format!("total amount err: {e}"),
514 Unparsable => "value balance is unparsable".to_string(),
515 })
516 }
517}
518
519impl<C> std::ops::Add for ValueBalance<C>
520where
521 C: Constraint,
522{
523 type Output = Result<ValueBalance<C>, ValueBalanceError>;
524 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
525 Ok(ValueBalance::<C> {
526 transparent: (self.transparent + rhs.transparent).map_err(Transparent)?,
527 sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
528 sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
529 orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
530 deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
531 ironwood: (self.ironwood + rhs.ironwood).map_err(Ironwood)?,
532 })
533 }
534}
535
536impl<C> std::ops::Add<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
537where
538 C: Constraint,
539{
540 type Output = Result<ValueBalance<C>, ValueBalanceError>;
541 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
542 self? + rhs
543 }
544}
545
546impl<C> std::ops::Add<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
547where
548 C: Constraint,
549{
550 type Output = Result<ValueBalance<C>, ValueBalanceError>;
551
552 fn add(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
553 self + rhs?
554 }
555}
556
557impl<C> std::ops::AddAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
558where
559 ValueBalance<C>: Copy,
560 C: Constraint,
561{
562 fn add_assign(&mut self, rhs: ValueBalance<C>) {
563 if let Ok(lhs) = *self {
564 *self = lhs + rhs;
565 }
566 }
567}
568
569impl<C> std::ops::Sub for ValueBalance<C>
570where
571 C: Constraint,
572{
573 type Output = Result<ValueBalance<C>, ValueBalanceError>;
574 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
575 Ok(ValueBalance::<C> {
576 transparent: (self.transparent - rhs.transparent).map_err(Transparent)?,
577 sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
578 sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
579 orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
580 deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
581 ironwood: (self.ironwood - rhs.ironwood).map_err(Ironwood)?,
582 })
583 }
584}
585impl<C> std::ops::Sub<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
586where
587 C: Constraint,
588{
589 type Output = Result<ValueBalance<C>, ValueBalanceError>;
590 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
591 self? - rhs
592 }
593}
594
595impl<C> std::ops::Sub<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
596where
597 C: Constraint,
598{
599 type Output = Result<ValueBalance<C>, ValueBalanceError>;
600
601 fn sub(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
602 self - rhs?
603 }
604}
605
606impl<C> std::ops::SubAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
607where
608 ValueBalance<C>: Copy,
609 C: Constraint,
610{
611 fn sub_assign(&mut self, rhs: ValueBalance<C>) {
612 if let Ok(lhs) = *self {
613 *self = lhs - rhs;
614 }
615 }
616}
617
618impl<C> std::iter::Sum<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
619where
620 C: Constraint + Copy,
621{
622 fn sum<I: Iterator<Item = ValueBalance<C>>>(mut iter: I) -> Self {
623 iter.try_fold(ValueBalance::zero(), |acc, value_balance| {
624 acc + value_balance
625 })
626 }
627}
628
629impl<'amt, C> std::iter::Sum<&'amt ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
630where
631 C: Constraint + std::marker::Copy + 'amt,
632{
633 fn sum<I: Iterator<Item = &'amt ValueBalance<C>>>(iter: I) -> Self {
634 iter.copied().sum()
635 }
636}
637
638impl<C> std::ops::Neg for ValueBalance<C>
639where
640 C: Constraint,
641{
642 type Output = ValueBalance<NegativeAllowed>;
643
644 fn neg(self) -> Self::Output {
645 ValueBalance::<NegativeAllowed> {
646 transparent: self.transparent.neg(),
647 sprout: self.sprout.neg(),
648 sapling: self.sapling.neg(),
649 orchard: self.orchard.neg(),
650 deferred: self.deferred.neg(),
651 ironwood: self.ironwood.neg(),
652 }
653 }
654}