# VAL-011: Conservation Check

> **Authoritative requirement:** [VAL-011](../NORMATIVE.md#VAL-011)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 6.1 Step 6

---

## Summary

The conservation check is the fundamental economic invariant of the Chia-compatible UTXO model: the total value removed from the UTXO set must be greater than or equal to the total value added, with the difference being the transaction fee. Formally, `sum(removal_amounts) >= sum(addition_amounts) + fee`. This prevents coins from being created out of thin air and ensures that the total supply of the token is non-inflationary at the transaction level.

In `dig-clvm`, the conservation check is performed after all spend bundles have been executed and their additions and removals collected. For each spend bundle, the removal amounts are the `amount` fields of each `CoinSpend.coin` in the bundle (the coins being spent), and the addition amounts are the `amount` fields of each coin created via `CREATE_COIN` conditions. The fee is computed as the difference: `fee = sum(removals) - sum(additions)`. If this value is negative, the spend bundle is invalid because it would create value from nothing.

On Chia L1, this check is performed in `validate_block_body()` at [`block_body_validation.py:191`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py#L191), where the block validator sums all coin amounts being spent and all coin amounts being created, then verifies the conservation property. The `dig-clvm` implementation performs the same check at the spend bundle level during `validate_spend_bundle()`, returning a `ConservationViolation` error that includes both the total input and total output amounts for diagnostic purposes.

---

## Specification

### Conservation Rule

The conservation invariant is expressed as:

```
sum(removal.amount for removal in removals) >= sum(addition.amount for addition in additions)
```

Where:
- **removals** are the coins being spent (inputs)
- **additions** are the coins being created via `CREATE_COIN` (outputs)

The fee is the non-negative remainder:

```
fee = sum(removal.amount) - sum(addition.amount)
```

### Implementation

```rust
/// Verify that inputs >= outputs (conservation of value).
/// Returns the fee (inputs - outputs) on success.
fn check_conservation(
    removals: &[Coin],
    additions: &[Coin],
) -> Result<u64, ValidationError> {
    let total_removed: u128 = removals.iter().map(|c| c.amount as u128).sum();
    let total_added: u128 = additions.iter().map(|c| c.amount as u128).sum();

    if total_removed < total_added {
        return Err(ValidationError::ConservationViolation {
            input_total: total_removed,
            output_total: total_added,
        });
    }

    // Fee fits in u64 because total_removed <= sum of all u64 values
    let fee = (total_removed - total_added) as u64;
    Ok(fee)
}
```

### Error Type

```rust
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationError {
    // ... other variants ...

    /// The spend bundle attempts to create more value than it consumes.
    /// input_total and output_total are the sums (in mojos) of removals
    /// and additions respectively.
    ConservationViolation {
        input_total: u128,
        output_total: u128,
    },
}
```

### Overflow Protection

Individual coin amounts are `u64`. When summing, the implementation uses `u128` to prevent overflow. The maximum possible sum is `u64::MAX * max_coins_per_bundle`, which fits comfortably in `u128`. The resulting fee is guaranteed to fit in `u64` because `fee <= total_removed <= u64::MAX * max_coins` and in practice is bounded by `MAX_COIN_AMOUNT`.

### Integration Point

The conservation check is Step 6 in the validation pipeline, after:
1. Coin existence check (VAL-004)
2. Already-spent check (VAL-005)
3. Puzzle hash verification (VAL-006)
4. CLVM execution and cost enforcement (VAL-007, VAL-008)
5. BLS signature verification (VAL-012)

```rust
// In validate_spend_bundle():
let fee = check_conservation(&removals, &additions)?;

Ok(SpendResult {
    additions,
    removals,
    fee,
    conditions: owned_conditions,
})
```

---

## Acceptance Criteria

- [ ] A spend bundle where `sum(removal amounts) > sum(addition amounts)` passes validation and returns the correct fee.
- [ ] A spend bundle where `sum(removal amounts) == sum(addition amounts)` passes validation with fee = 0.
- [ ] A spend bundle where `sum(removal amounts) < sum(addition amounts)` is rejected with `ConservationViolation` containing correct input/output totals.
- [ ] The `ConservationViolation` error variant includes both `input_total` and `output_total` fields.
- [ ] Intermediate sums use `u128` to prevent overflow when summing many large `u64` amounts.
- [ ] The computed fee is included in the `SpendResult` output structure (VAL-014).

---

## Implementation Notes

- **Primary file:** `src/consensus/validate.rs` (conservation check function and its call site in `validate_spend_bundle`).
- **u128 arithmetic:** Even though `u64` overflow is practically impossible (would require spending more than 18 quintillion mojos in a single bundle), using `u128` is a defensive measure that costs nothing in performance and prevents any theoretical overflow.
- **Zero-fee transactions:** A fee of zero is perfectly valid. The conservation check only requires non-negative fee.
- **Ephemeral coins:** Ephemeral coins (VAL-010) participate in the conservation check normally. When an ephemeral coin is spent, its amount is included in the removal sum.
- **Negative amounts impossible:** Coin amounts are `u64` (unsigned), so negative amounts are structurally impossible. This simplifies the conservation check to a straightforward comparison.
- **Block-level vs bundle-level:** The check is performed per-bundle in `validate_spend_bundle()`. At the block level (`validate_block`), the aggregate conservation property holds because each individual bundle conserves value.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Positive fee | Unit test | Removals sum to 1000, additions sum to 900 | Ok(fee=100) |
| Zero fee | Unit test | Removals sum to 500, additions sum to 500 | Ok(fee=0) |
| Violation detected | Unit test | Removals sum to 100, additions sum to 200 | Err(ConservationViolation { input: 100, output: 200 }) |
| Large amounts no overflow | Unit test | Multiple coins near u64::MAX | Correct result via u128 arithmetic |
| Single coin in, multiple out | Simulator test | 1 coin of 1000 spent, 3 coins of 300 created | Ok(fee=100) |
| Multiple coins in, single out | Simulator test | 3 coins of 300 spent, 1 coin of 850 created | Ok(fee=50) |
| Empty additions | Unit test | Coins spent with no CREATE_COIN outputs | Ok(fee=total_removed), all value is fee |

---

## Source Citations

- **SPEC.md Section 6.1 Step 6**: Conservation check — "sum(removal amounts) >= sum(addition amounts) + fee."
- **SPEC.md Section 5.1**: `SpendResult.fee` field definition — "The difference between input and output amounts."
- **L1 reference — validate_block_body:** [`block_body_validation.py:191`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/block_body_validation.py#L191) — block-level conservation enforcement.
- **L1 reference — generator_tools.py:54:** `tx_removals_and_additions` — extraction of additions and removals for conservation check.

---

## References

- [VAL-004](VAL-004.md) — Coin existence check (removals must exist)
- [VAL-007](VAL-007.md) — Cost enforcement (precedes conservation check)
- [VAL-010](VAL-010.md) — Ephemeral coins (participate in conservation normally)
- [VAL-012](VAL-012.md) — BLS signature verification (precedes conservation check)
- [VAL-014](VAL-014.md) — SpendResult output structure (fee is a field)
