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 constrain<C2>(self) -> Result<ValueBalance<C2>, ValueBalanceError>
166 where
167 C2: Constraint,
168 {
169 Ok(ValueBalance::<C2> {
170 transparent: self.transparent.constrain().map_err(Transparent)?,
171 sprout: self.sprout.constrain().map_err(Sprout)?,
172 sapling: self.sapling.constrain().map_err(Sapling)?,
173 orchard: self.orchard.constrain().map_err(Orchard)?,
174 deferred: self.deferred.constrain().map_err(Deferred)?,
175 ironwood: self.ironwood.constrain().map_err(Ironwood)?,
176 })
177 }
178}
179
180impl ValueBalance<NegativeAllowed> {
181 pub fn remaining_transaction_value(&self) -> Result<Amount<NonNegative>, amount::Error> {
194 (self.transparent + self.sprout + self.sapling + self.orchard + self.ironwood)?
203 .constrain::<NonNegative>()
204 }
205}
206
207impl ValueBalance<NonNegative> {
208 #[cfg(any(test, feature = "proptest-impl"))]
230 pub fn add_transaction(
231 self,
232 transaction: impl Borrow<Transaction>,
233 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
234 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
235 use std::ops::Neg;
236
237 let chain_value_pool_change = transaction
240 .borrow()
241 .value_balance_from_outputs(utxos)?
242 .neg();
243
244 self.add_chain_value_pool_change(chain_value_pool_change)
245 }
246
247 #[cfg(any(test, feature = "proptest-impl"))]
255 pub fn add_transparent_input(
256 self,
257 input: impl Borrow<transparent::Input>,
258 utxos: &HashMap<transparent::OutPoint, transparent::Output>,
259 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
260 use std::ops::Neg;
261
262 let transparent_value_pool_change = input.borrow().value_from_outputs(utxos).neg();
265 let transparent_value_pool_change =
266 ValueBalance::from_transparent_amount(transparent_value_pool_change);
267
268 self.add_chain_value_pool_change(transparent_value_pool_change)
269 }
270
271 #[allow(clippy::unwrap_in_result)]
312 pub fn add_chain_value_pool_change(
313 self,
314 chain_value_pool_change: ValueBalance<NegativeAllowed>,
315 ) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
316 let mut chain_value_pool = self
317 .constrain::<NegativeAllowed>()
318 .expect("conversion from NonNegative to NegativeAllowed is always valid");
319 chain_value_pool = (chain_value_pool + chain_value_pool_change)?;
320
321 chain_value_pool.constrain()
322 }
323
324 #[cfg(any(test, feature = "proptest-impl"))]
328 pub fn fake_populated_pool() -> ValueBalance<NonNegative> {
329 let mut fake_value_pool = ValueBalance::zero();
330
331 let fake_transparent_value_balance =
332 ValueBalance::from_transparent_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
333 let fake_sprout_value_balance =
334 ValueBalance::from_sprout_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
335 let fake_sapling_value_balance =
336 ValueBalance::from_sapling_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
337 let fake_orchard_value_balance =
338 ValueBalance::from_orchard_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
339 let fake_ironwood_value_balance =
340 ValueBalance::from_ironwood_amount(Amount::try_from(MAX_MONEY / 2).unwrap());
341
342 fake_value_pool.set_transparent_value_balance(fake_transparent_value_balance);
343 fake_value_pool.set_sprout_value_balance(fake_sprout_value_balance);
344 fake_value_pool.set_sapling_value_balance(fake_sapling_value_balance);
345 fake_value_pool.set_orchard_value_balance(fake_orchard_value_balance);
346 fake_value_pool.set_ironwood_value_balance(fake_ironwood_value_balance);
347
348 fake_value_pool
349 }
350
351 pub fn to_bytes(self) -> [u8; 48] {
357 match [
358 self.transparent.to_bytes(),
359 self.sprout.to_bytes(),
360 self.sapling.to_bytes(),
361 self.orchard.to_bytes(),
362 self.deferred.to_bytes(),
363 self.ironwood.to_bytes(),
364 ]
365 .concat()
366 .try_into()
367 {
368 Ok(bytes) => bytes,
369 _ => unreachable!(
370 "six [u8; 8] should always concat with no error into a single [u8; 48]"
371 ),
372 }
373 }
374
375 #[allow(clippy::unwrap_in_result)]
380 pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
381 let bytes_length = bytes.len();
382
383 match bytes_length {
385 32 | 40 | 48 => {}
386 _ => return Err(Unparsable),
387 };
388
389 let transparent = Amount::from_bytes(
390 bytes[0..8]
391 .try_into()
392 .expect("transparent amount should be parsable"),
393 )
394 .map_err(Transparent)?;
395
396 let sprout = Amount::from_bytes(
397 bytes[8..16]
398 .try_into()
399 .expect("sprout amount should be parsable"),
400 )
401 .map_err(Sprout)?;
402
403 let sapling = Amount::from_bytes(
404 bytes[16..24]
405 .try_into()
406 .expect("sapling amount should be parsable"),
407 )
408 .map_err(Sapling)?;
409
410 let orchard = Amount::from_bytes(
411 bytes[24..32]
412 .try_into()
413 .expect("orchard amount should be parsable"),
414 )
415 .map_err(Orchard)?;
416
417 let deferred = match bytes_length {
418 32 => Amount::zero(),
419 40 | 48 => Amount::from_bytes(
420 bytes[32..40]
421 .try_into()
422 .expect("deferred amount should be parsable"),
423 )
424 .map_err(Deferred)?,
425 _ => return Err(Unparsable),
426 };
427
428 let ironwood = match bytes_length {
429 32 | 40 => Amount::zero(),
430 48 => Amount::from_bytes(
431 bytes[40..48]
432 .try_into()
433 .expect("ironwood amount should be parsable"),
434 )
435 .map_err(Ironwood)?,
436 _ => return Err(Unparsable),
437 };
438
439 Ok(ValueBalance {
440 transparent,
441 sprout,
442 sapling,
443 orchard,
444 deferred,
445 ironwood,
446 })
447 }
448}
449
450#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
451pub enum ValueBalanceError {
453 Transparent(amount::Error),
455
456 Sprout(amount::Error),
458
459 Sapling(amount::Error),
461
462 Orchard(amount::Error),
464
465 Deferred(amount::Error),
467
468 Ironwood(amount::Error),
470
471 Unparsable,
473}
474
475impl fmt::Display for ValueBalanceError {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 f.write_str(&match self {
478 Transparent(e) => format!("transparent amount err: {e}"),
479 Sprout(e) => format!("sprout amount err: {e}"),
480 Sapling(e) => format!("sapling amount err: {e}"),
481 Orchard(e) => format!("orchard amount err: {e}"),
482 Deferred(e) => format!("deferred amount err: {e}"),
483 Ironwood(e) => format!("ironwood amount err: {e}"),
484 Unparsable => "value balance is unparsable".to_string(),
485 })
486 }
487}
488
489impl<C> std::ops::Add for ValueBalance<C>
490where
491 C: Constraint,
492{
493 type Output = Result<ValueBalance<C>, ValueBalanceError>;
494 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
495 Ok(ValueBalance::<C> {
496 transparent: (self.transparent + rhs.transparent).map_err(Transparent)?,
497 sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
498 sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
499 orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
500 deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
501 ironwood: (self.ironwood + rhs.ironwood).map_err(Ironwood)?,
502 })
503 }
504}
505
506impl<C> std::ops::Add<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
507where
508 C: Constraint,
509{
510 type Output = Result<ValueBalance<C>, ValueBalanceError>;
511 fn add(self, rhs: ValueBalance<C>) -> Self::Output {
512 self? + rhs
513 }
514}
515
516impl<C> std::ops::Add<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
517where
518 C: Constraint,
519{
520 type Output = Result<ValueBalance<C>, ValueBalanceError>;
521
522 fn add(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
523 self + rhs?
524 }
525}
526
527impl<C> std::ops::AddAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
528where
529 ValueBalance<C>: Copy,
530 C: Constraint,
531{
532 fn add_assign(&mut self, rhs: ValueBalance<C>) {
533 if let Ok(lhs) = *self {
534 *self = lhs + rhs;
535 }
536 }
537}
538
539impl<C> std::ops::Sub for ValueBalance<C>
540where
541 C: Constraint,
542{
543 type Output = Result<ValueBalance<C>, ValueBalanceError>;
544 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
545 Ok(ValueBalance::<C> {
546 transparent: (self.transparent - rhs.transparent).map_err(Transparent)?,
547 sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
548 sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
549 orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
550 deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
551 ironwood: (self.ironwood - rhs.ironwood).map_err(Ironwood)?,
552 })
553 }
554}
555impl<C> std::ops::Sub<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
556where
557 C: Constraint,
558{
559 type Output = Result<ValueBalance<C>, ValueBalanceError>;
560 fn sub(self, rhs: ValueBalance<C>) -> Self::Output {
561 self? - rhs
562 }
563}
564
565impl<C> std::ops::Sub<Result<ValueBalance<C>, ValueBalanceError>> for ValueBalance<C>
566where
567 C: Constraint,
568{
569 type Output = Result<ValueBalance<C>, ValueBalanceError>;
570
571 fn sub(self, rhs: Result<ValueBalance<C>, ValueBalanceError>) -> Self::Output {
572 self - rhs?
573 }
574}
575
576impl<C> std::ops::SubAssign<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
577where
578 ValueBalance<C>: Copy,
579 C: Constraint,
580{
581 fn sub_assign(&mut self, rhs: ValueBalance<C>) {
582 if let Ok(lhs) = *self {
583 *self = lhs - rhs;
584 }
585 }
586}
587
588impl<C> std::iter::Sum<ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
589where
590 C: Constraint + Copy,
591{
592 fn sum<I: Iterator<Item = ValueBalance<C>>>(mut iter: I) -> Self {
593 iter.try_fold(ValueBalance::zero(), |acc, value_balance| {
594 acc + value_balance
595 })
596 }
597}
598
599impl<'amt, C> std::iter::Sum<&'amt ValueBalance<C>> for Result<ValueBalance<C>, ValueBalanceError>
600where
601 C: Constraint + std::marker::Copy + 'amt,
602{
603 fn sum<I: Iterator<Item = &'amt ValueBalance<C>>>(iter: I) -> Self {
604 iter.copied().sum()
605 }
606}
607
608impl<C> std::ops::Neg for ValueBalance<C>
609where
610 C: Constraint,
611{
612 type Output = ValueBalance<NegativeAllowed>;
613
614 fn neg(self) -> Self::Output {
615 ValueBalance::<NegativeAllowed> {
616 transparent: self.transparent.neg(),
617 sprout: self.sprout.neg(),
618 sapling: self.sapling.neg(),
619 orchard: self.orchard.neg(),
620 deferred: self.deferred.neg(),
621 ironwood: self.ironwood.neg(),
622 }
623 }
624}