Skip to main content

zebra_chain/
value_balance.rs

1//! Balances in chain value pools and transaction value pools.
2
3use 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/// A balance in each chain value pool or transaction value pool.
22#[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    /// Creates a [`ValueBalance`] from the given transparent amount.
37    pub fn from_transparent_amount(transparent_amount: Amount<C>) -> Self {
38        ValueBalance {
39            transparent: transparent_amount,
40            ..ValueBalance::zero()
41        }
42    }
43
44    /// Creates a [`ValueBalance`] from the given sprout amount.
45    pub fn from_sprout_amount(sprout_amount: Amount<C>) -> Self {
46        ValueBalance {
47            sprout: sprout_amount,
48            ..ValueBalance::zero()
49        }
50    }
51
52    /// Creates a [`ValueBalance`] from the given sapling amount.
53    pub fn from_sapling_amount(sapling_amount: Amount<C>) -> Self {
54        ValueBalance {
55            sapling: sapling_amount,
56            ..ValueBalance::zero()
57        }
58    }
59
60    /// Creates a [`ValueBalance`] from the given orchard amount.
61    pub fn from_orchard_amount(orchard_amount: Amount<C>) -> Self {
62        ValueBalance {
63            orchard: orchard_amount,
64            ..ValueBalance::zero()
65        }
66    }
67
68    /// Creates a [`ValueBalance`] from the given ironwood amount.
69    pub fn from_ironwood_amount(ironwood_amount: Amount<C>) -> Self {
70        ValueBalance {
71            ironwood: ironwood_amount,
72            ..ValueBalance::zero()
73        }
74    }
75
76    /// Get the transparent amount from the [`ValueBalance`].
77    pub fn transparent_amount(&self) -> Amount<C> {
78        self.transparent
79    }
80
81    /// Insert a transparent value balance into a given [`ValueBalance`]
82    /// leaving the other values untouched.
83    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    /// Get the sprout amount from the [`ValueBalance`].
92    pub fn sprout_amount(&self) -> Amount<C> {
93        self.sprout
94    }
95
96    /// Insert a sprout value balance into a given [`ValueBalance`]
97    /// leaving the other values untouched.
98    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    /// Get the sapling amount from the [`ValueBalance`].
104    pub fn sapling_amount(&self) -> Amount<C> {
105        self.sapling
106    }
107
108    /// Insert a sapling value balance into a given [`ValueBalance`]
109    /// leaving the other values untouched.
110    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    /// Get the orchard amount from the [`ValueBalance`].
116    pub fn orchard_amount(&self) -> Amount<C> {
117        self.orchard
118    }
119
120    /// Insert an orchard value balance into a given [`ValueBalance`]
121    /// leaving the other values untouched.
122    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    /// Returns the deferred amount.
128    pub fn deferred_amount(&self) -> Amount<C> {
129        self.deferred
130    }
131
132    /// Sets the deferred amount without affecting other amounts.
133    pub fn set_deferred_amount(&mut self, deferred_amount: Amount<C>) -> &Self {
134        self.deferred = deferred_amount;
135        self
136    }
137
138    /// Get the ironwood amount from the [`ValueBalance`].
139    pub fn ironwood_amount(&self) -> Amount<C> {
140        self.ironwood
141    }
142
143    /// Insert an ironwood value balance into a given [`ValueBalance`]
144    /// leaving the other values untouched.
145    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    /// Creates a [`ValueBalance`] where all the pools are zero.
151    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    /// Returns the sum of all value pool balances.
164    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    /// Convert this value balance to a different ValueBalance type,
181    /// if it satisfies the new constraint
182    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    /// Assumes that this value balance is a non-coinbase transaction value balance,
199    /// and returns the remaining value in the transaction value pool.
200    ///
201    /// # Consensus
202    ///
203    /// > The remaining value in the transparent transaction value pool MUST be nonnegative.
204    ///
205    /// <https://zips.z.cash/protocol/protocol.pdf#transactions>
206    ///
207    /// This rule applies to Block and Mempool transactions.
208    ///
209    /// Design: <https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/rfcs/0012-value-pools.md#definitions>
210    pub fn remaining_transaction_value(&self) -> Result<Amount<NonNegative>, amount::Error> {
211        // Calculated by summing the transparent, sprout, sapling, orchard, and ironwood value
212        // balances, as specified in:
213        // https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions
214        //
215        // The ironwood bundle (NU6.3 onward) contributes to the transaction value pool exactly
216        // like the orchard bundle; it is zero for transactions without an ironwood bundle.
217        //
218        // This will error if the remaining value in the transaction value pool is negative.
219        (self.transparent + self.sprout + self.sapling + self.orchard + self.ironwood)?
220            .constrain::<NonNegative>()
221    }
222}
223
224impl ValueBalance<NonNegative> {
225    /// Returns the sum of this value balance, and the chain value pool changes in `transaction`.
226    ///
227    /// `outputs` must contain the [`transparent::Output`]s of every input in this transaction,
228    /// including UTXOs created by earlier transactions in its block.
229    ///
230    /// Note: the chain value pool has the opposite sign to the transaction
231    /// value pool.
232    ///
233    /// # Consensus
234    ///
235    /// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
236    /// > "Orchard chain value pool balance" would become negative in the block chain created
237    /// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
238    /// >
239    /// > Nodes MAY relay transactions even if one or more of them cannot be mined due to the
240    /// > aforementioned restriction.
241    ///
242    /// <https://zips.z.cash/zip-0209#specification>
243    ///
244    /// Since this consensus rule is optional for mempool transactions,
245    /// Zebra does not check it in the mempool transaction verifier.
246    #[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        // the chain pool (unspent outputs) has the opposite sign to
255        // transaction value balances (inputs - outputs)
256        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    /// Returns the sum of this value balance, and the chain value pool change in `input`.
268    ///
269    /// `outputs` must contain the [`transparent::Output`] spent by `input`,
270    /// (including UTXOs created by earlier transactions in its block).
271    ///
272    /// Note: the chain value pool has the opposite sign to the transaction
273    /// value pool. Inputs remove value from the chain value pool.
274    #[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        // the chain pool (unspent outputs) has the opposite sign to
283        // transaction value balances (inputs - outputs)
284        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    /// Returns the sum of this value balance, and the given `chain_value_pool_change`.
292    ///
293    /// Note that the chain value pool has the opposite sign to the transaction value pool.
294    ///
295    /// # Consensus
296    ///
297    /// > If the Sprout chain value pool balance would become negative in the block chain
298    /// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
299    ///
300    /// <https://zips.z.cash/protocol/protocol.pdf#joinsplitbalance>
301    ///
302    /// > If the Sapling chain value pool balance would become negative in the block chain
303    /// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
304    ///
305    /// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
306    ///
307    /// > If the Orchard chain value pool balance would become negative in the block chain
308    /// > created as a result of accepting a block , then all nodes MUST reject the block as invalid.
309    ///
310    /// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
311    ///
312    /// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
313    /// > "Orchard chain value pool balance" would become negative in the block chain created
314    /// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
315    ///
316    /// <https://zips.z.cash/zip-0209#specification>
317    ///
318    /// Zebra also checks that the transparent value pool is non-negative.
319    /// In Zebra, we define this pool as the sum of all unspent transaction outputs.
320    /// (Despite their encoding as an `int64`, transparent output values must be non-negative.)
321    ///
322    /// This is a consensus rule derived from Bitcoin:
323    ///
324    /// > because a UTXO can only be spent once,
325    /// > the full value of the included UTXOs must be spent or given to a miner as a transaction fee.
326    ///
327    /// <https://developer.bitcoin.org/devguide/transactions.html#transaction-fees-and-change>
328    ///
329    /// We implement the consensus rules above by constraining the returned value balance to
330    /// [`ValueBalance<NonNegative>`].
331    #[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        // The sum of all chain value pools is the total monetary base, which consensus caps at
344        // `MAX_MONEY`. Reject any change that would push the chain value pool total over that cap.
345        chain_value_pool.total().map_err(ValueBalanceError::Total)?;
346
347        Ok(chain_value_pool)
348    }
349
350    /// Create a fake value pool for testing purposes.
351    ///
352    /// The resulting [`ValueBalance`] has `MAX_MONEY / 8` on the transparent, Sprout, Sapling,
353    /// Orchard, and Ironwood pools; the deferred pool is zero. This keeps the total within the
354    /// valid `Amount` range (see [`ValueBalance::total`]), while leaving headroom for value pool
355    /// changes that tests commit on top of it.
356    #[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    /// To byte array
381    ///
382    /// The `ironwood` pool (NU6.3 onward) is appended after `deferred`, so that records written by
383    /// earlier Zebra versions (32 bytes without `deferred`, or 40 bytes with it) remain parsable by
384    /// [`Self::from_bytes`].
385    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    /// From byte array
405    ///
406    /// Accepts 32-byte (pre-`deferred`), 40-byte (pre-`ironwood`), and 48-byte records; missing
407    /// trailing pools default to zero.
408    #[allow(clippy::unwrap_in_result)]
409    pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
410        let bytes_length = bytes.len();
411
412        // Return an error early if bytes don't have the right length instead of panicking later.
413        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)]
480/// Errors that can be returned when validating a [`ValueBalance`]
481pub enum ValueBalanceError {
482    /// transparent amount error {0}
483    Transparent(amount::Error),
484
485    /// sprout amount error {0}
486    Sprout(amount::Error),
487
488    /// sapling amount error {0}
489    Sapling(amount::Error),
490
491    /// orchard amount error {0}
492    Orchard(amount::Error),
493
494    /// deferred amount error {0}
495    Deferred(amount::Error),
496
497    /// ironwood amount error {0}
498    Ironwood(amount::Error),
499
500    /// total amount error {0}
501    Total(amount::Error),
502
503    /// ValueBalance is unparsable
504    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}