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 tx = transaction.borrow();
257 let transparent = tx.transparent_value_balance_from_outputs(utxos)?;
258 let sprout = tx.sprout_value_balance()?;
259 let sapling = tx.sapling_value_balance();
260 let orchard = tx.orchard_value_balance();
261 let ironwood = tx.ironwood_value_balance();
262 let chain_value_pool_change = (transparent + sprout + sapling + orchard + ironwood)?.neg();
263
264 self.add_chain_value_pool_change(chain_value_pool_change)
265 }
266
267 #[cfg(any(test, feature = "proptest-impl"))]
275 pub fn add_transparent_input(
276 self,
277 input: impl Borrow<transparent::Input>,
278 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
279 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
280 use std::ops::Neg;
281
282 let transparent_value_pool_change = input.borrow().value_from_outputs(utxos).neg();
285 let transparent_value_pool_change =
286 ValueBalance::from_transparent_amount(transparent_value_pool_change);
287
288 self.add_chain_value_pool_change(transparent_value_pool_change)
289 }
290
291 #[allow(clippy::unwrap_in_result)]
332 pub fn add_chain_value_pool_change(
333 self,
334 chain_value_pool_change: ValueBalance<NegativeAllowed>,
335 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
336 let mut chain_value_pool = self
337 .constrain::<NegativeAllowed>()
338 .expect("conversion from NonNegative to NegativeAllowed is always valid");
339 chain_value_pool = (chain_value_pool + chain_value_pool_change)?;
340
341 let chain_value_pool = chain_value_pool.constrain::<NonNegative>()?;
342
343 chain_value_pool.total().map_err(ValueBalanceError::Total)?;
346
347 Ok(chain_value_pool)
348 }
349
350 #[cfg(any(test, feature = "proptest-impl"))]
357 pub fn fake_populated_pool() -> ValueBalance<NonNegative> {
358 let mut fake_value_pool = ValueBalance::zero();
359
360 let fake_transparent_value_balance =
361 ValueBalance::from_transparent_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
362 let fake_sprout_value_balance =
363 ValueBalance::from_sprout_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
364 let fake_sapling_value_balance =
365 ValueBalance::from_sapling_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
366 let fake_orchard_value_balance =
367 ValueBalance::from_orchard_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
368 let fake_ironwood_value_balance =
369 ValueBalance::from_ironwood_amount(Amount::try_from(MAX_MONEY / 8).unwrap());
370
371 fake_value_pool.set_transparent_value_balance(fake_transparent_value_balance);
372 fake_value_pool.set_sprout_value_balance(fake_sprout_value_balance);
373 fake_value_pool.set_sapling_value_balance(fake_sapling_value_balance);
374 fake_value_pool.set_orchard_value_balance(fake_orchard_value_balance);
375 fake_value_pool.set_ironwood_value_balance(fake_ironwood_value_balance);
376
377 fake_value_pool
378 }
379
380 pub fn to_bytes(self) -> [u8; 48] {
386 match [
387 self.transparent.to_bytes(),
388 self.sprout.to_bytes(),
389 self.sapling.to_bytes(),
390 self.orchard.to_bytes(),
391 self.deferred.to_bytes(),
392 self.ironwood.to_bytes(),
393 ]
394 .concat()
395 .try_into()
396 {
397 Ok(bytes) => bytes,
398 _ => unreachable!(
399 "six [u8; 8] should always concat with no error into a single [u8; 48]"
400 ),
401 }
402 }
403
404 #[allow(clippy::unwrap_in_result)]
409 pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
410 let bytes_length = bytes.len();
411
412 match bytes_length {
414 32 | 40 | 48 => {}
415 _ => return Err(Unparsable),
416 };
417
418 let transparent = Amount::from_bytes(
419 bytes[0..8]
420 .try_into()
421 .expect("transparent amount should be parsable"),
422 )
423 .map_err(Transparent)?;
424
425 let sprout = Amount::from_bytes(
426 bytes[8..16]
427 .try_into()
428 .expect("sprout amount should be parsable"),
429 )
430 .map_err(Sprout)?;
431
432 let sapling = Amount::from_bytes(
433 bytes[16..24]
434 .try_into()
435 .expect("sapling amount should be parsable"),
436 )
437 .map_err(Sapling)?;
438
439 let orchard = Amount::from_bytes(
440 bytes[24..32]
441 .try_into()
442 .expect("orchard amount should be parsable"),
443 )
444 .map_err(Orchard)?;
445
446 let deferred = match bytes_length {
447 32 => Amount::zero(),
448 40 | 48 => Amount::from_bytes(
449 bytes[32..40]
450 .try_into()
451 .expect("deferred amount should be parsable"),
452 )
453 .map_err(Deferred)?,
454 _ => return Err(Unparsable),
455 };
456
457 let ironwood = match bytes_length {
458 32 | 40 => Amount::zero(),
459 48 => Amount::from_bytes(
460 bytes[40..48]
461 .try_into()
462 .expect("ironwood amount should be parsable"),
463 )
464 .map_err(Ironwood)?,
465 _ => return Err(Unparsable),
466 };
467
468 Ok(ValueBalance {
469 transparent,
470 sprout,
471 sapling,
472 orchard,
473 deferred,
474 ironwood,
475 })
476 }
477}
478
479#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
480pub enum ValueBalanceError {
482 Transparent(amount::Error),
484
485 Sprout(amount::Error),
487
488 Sapling(amount::Error),
490
491 Orchard(amount::Error),
493
494 Deferred(amount::Error),
496
497 Ironwood(amount::Error),
499
500 Total(amount::Error),
502
503 Unparsable,
505}
506
507impl fmt::Display for ValueBalanceError {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 f.write_str(&match self {
510 Transparent(e) => format!("transparent amount err: {e}"),
511 Sprout(e) => format!("sprout amount err: {e}"),
512 Sapling(e) => format!("sapling amount err: {e}"),
513 Orchard(e) => format!("orchard amount err: {e}"),
514 Deferred(e) => format!("deferred amount err: {e}"),
515 Ironwood(e) => format!("ironwood amount err: {e}"),
516 Total(e) => format!("total amount err: {e}"),
517 Unparsable => "value balance is unparsable".to_string(),
518 })
519 }
520}
521
522impl<C> std::ops::Add for ValueBalance<C>
523where
524 C: Constraint,
525{
526 type Output = Result<ValueBalance<C>, ValueBalanceError>;
527 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
528 Ok(ValueBalance::<C> {
529 transparent: (self.transparent + rhs.transparent).map_err(Transparent)?,
530 sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
531 sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
532 orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
533 deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
534 ironwood: (self.ironwood + rhs.ironwood).map_err(Ironwood)?,
535 })
536 }
537}
538
539impl<C> std::ops::Add<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
540where
541 C: Constraint,
542{
543 type Output = Result<ValueBalance<C>, ValueBalanceError>;
544 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
545 self? + rhs
546 }
547}
548
549impl<C> std::ops::Add<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
550where
551 C: Constraint,
552{
553 type Output = Result<ValueBalance<C>, ValueBalanceError>;
554
555 fn add(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
556 self + rhs?
557 }
558}
559
560impl<C> std::ops::AddAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
561where
562 ValueBalance<C>: Copy,
563 C: Constraint,
564{
565 fn add_assign(&mut self, rhs: ValueBalance<C>) {
566 if let Ok(lhs) = *self {
567 *self = lhs + rhs;
568 }
569 }
570}
571
572impl<C> std::ops::Sub for ValueBalance<C>
573where
574 C: Constraint,
575{
576 type Output = Result<ValueBalance<C>, ValueBalanceError>;
577 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
578 Ok(ValueBalance::<C> {
579 transparent: (self.transparent - rhs.transparent).map_err(Transparent)?,
580 sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
581 sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
582 orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
583 deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
584 ironwood: (self.ironwood - rhs.ironwood).map_err(Ironwood)?,
585 })
586 }
587}
588impl<C> std::ops::Sub<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
589where
590 C: Constraint,
591{
592 type Output = Result<ValueBalance<C>, ValueBalanceError>;
593 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
594 self? - rhs
595 }
596}
597
598impl<C> std::ops::Sub<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
599where
600 C: Constraint,
601{
602 type Output = Result<ValueBalance<C>, ValueBalanceError>;
603
604 fn sub(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
605 self - rhs?
606 }
607}
608
609impl<C> std::ops::SubAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
610where
611 ValueBalance<C>: Copy,
612 C: Constraint,
613{
614 fn sub_assign(&mut self, rhs: ValueBalance<C>) {
615 if let Ok(lhs) = *self {
616 *self = lhs - rhs;
617 }
618 }
619}
620
621impl<C> std::iter::Sum<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
622where
623 C: Constraint + Copy,
624{
625 fn sum<I: Iterator<Item = ValueBalance<C>>>(mut iter: I) -> Self {
626 iter.try_fold(ValueBalance::zero(), |acc, value_balance| {
627 acc + value_balance
628 })
629 }
630}
631
632impl<'amt, C> std::iter::Sum<&'amt ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
633where
634 C: Constraint + std::marker::Copy + 'amt,
635{
636 fn sum<I: Iterator<Item = &'amt ValueBalance<C>>>(iter: I) -> Self {
637 iter.copied().sum()
638 }
639}
640
641impl<C> std::ops::Neg for ValueBalance<C>
642where
643 C: Constraint,
644{
645 type Output = ValueBalance<NegativeAllowed>;
646
647 fn neg(self) -> Self::Output {
648 ValueBalance::<NegativeAllowed> {
649 transparent: self.transparent.neg(),
650 sprout: self.sprout.neg(),
651 sapling: self.sapling.neg(),
652 orchard: self.orchard.neg(),
653 deferred: self.deferred.neg(),
654 ironwood: self.ironwood.neg(),
655 }
656 }
657}