# VAL-014: SpendResult Output Structure

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

---

## Summary

`SpendResult` is the output structure returned by `validate_spend_bundle()` upon successful validation. It contains all the information a caller needs to update the UTXO set, compute fees, and process conditions: the list of coins created (`additions`), the list of coins consumed (`removals`), the transaction fee, and the full set of parsed conditions from the CLVM execution. This structure is the primary interface between the validation layer and the calling application.

The design of `SpendResult` mirrors the information extracted by Chia L1's `tx_removals_and_additions()` in [`generator_tools.py:54`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/generator_tools.py#L54) and the `MempoolItem.conds` field in [`mempool_item.py:42`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_item.py#L42). By providing these fields in a single return value, `dig-clvm` allows callers to process the validation result without needing to re-derive any information from the raw spend bundle.

The `conditions` field wraps an `OwnedSpendBundleConditions` from `chia-consensus`, which contains all parsed conditions organized by type (timelocks, assertions, AGG_SIG pairs, CREATE_COIN, SEND_MESSAGE, etc.) along with the total CLVM cost. This is an owned (non-borrowed) type so the result can outlive the validation context and be stored, cached, or passed between threads.

---

## Specification

### SpendResult Structure

```rust
use chia_consensus::gen::owned_conditions::OwnedSpendBundleConditions;
use chia_protocol::Coin;

/// The result of successfully validating a spend bundle.
/// Contains all information needed by the caller to update
/// the UTXO set and process the transaction.
#[derive(Debug, Clone)]
pub struct SpendResult {
    /// Coins created by this spend bundle (from CREATE_COIN conditions).
    /// Each coin has parent_coin_info, puzzle_hash, and amount.
    pub additions: Vec<Coin>,

    /// Coins consumed (spent) by this spend bundle.
    /// These are the coins from each CoinSpend in the bundle.
    pub removals: Vec<Coin>,

    /// Transaction fee: sum(removal amounts) - sum(addition amounts).
    /// Always non-negative (guaranteed by conservation check VAL-011).
    pub fee: u64,

    /// All parsed conditions from CLVM execution, organized by type.
    /// Includes timelocks, assertions, AGG_SIG pairs, CREATE_COIN,
    /// SEND_MESSAGE, RECEIVE_MESSAGE, reserve fees, and CLVM cost.
    pub conditions: OwnedSpendBundleConditions,
}
```

### Fields in Detail

#### `additions: Vec<Coin>`

Created from `CREATE_COIN` conditions emitted during CLVM execution:

```rust
// Each CREATE_COIN condition produces a Coin:
let coin = Coin {
    parent_coin_info: spent_coin.coin_id(),  // parent is the coin being spent
    puzzle_hash: create_coin_condition.puzzle_hash,
    amount: create_coin_condition.amount,
};
```

The `additions` vector contains ALL coins created across ALL spends in the bundle. Order follows the execution order of the bundle's coin spends.

#### `removals: Vec<Coin>`

The coins being spent, extracted from each `CoinSpend` in the bundle:

```rust
// Each CoinSpend contributes one removal:
let removals: Vec<Coin> = spend_bundle
    .coin_spends
    .iter()
    .map(|cs| cs.coin.clone())
    .collect();
```

#### `fee: u64`

Computed during the conservation check (VAL-011):

```rust
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();
let fee = (total_removed - total_added) as u64;
```

#### `conditions: OwnedSpendBundleConditions`

The `OwnedSpendBundleConditions` type from `chia-consensus` contains:

```rust
// From chia-consensus (simplified):
pub struct OwnedSpendBundleConditions {
    pub spends: Vec<OwnedSpendConditions>,
    pub cost: u64,                    // Total CLVM execution cost
    pub removal_amount: u64,          // Sum of all removal amounts
    pub addition_amount: u64,         // Sum of all addition amounts
    pub agg_sig_unsafe: Vec<...>,     // AGG_SIG_UNSAFE pairs
    // ... other aggregated fields
}

pub struct OwnedSpendConditions {
    pub coin_id: Bytes32,
    pub parent_id: Bytes32,
    pub puzzle_hash: Bytes32,
    pub coin_amount: u64,
    pub height_relative: Option<u32>,
    pub seconds_relative: Option<u64>,
    pub before_height_relative: Option<u32>,
    pub before_seconds_relative: Option<u64>,
    pub birth_height: Option<u32>,
    pub birth_seconds: Option<u64>,
    pub create_coin: Vec<...>,
    pub agg_sig_me: Vec<...>,
    pub agg_sig_parent: Vec<...>,
    // ... per-spend condition fields
}
```

### Construction Point

`SpendResult` is assembled at the end of `validate_spend_bundle()`:

```rust
pub fn validate_spend_bundle(
    spend_bundle: &SpendBundle,
    context: &ValidationContext,
    config: &ValidationConfig,
    cache: Option<&mut BlsCache>,
) -> Result<SpendResult, ValidationError> {
    // Steps 1-5: existence, spent, puzzle hash, CLVM, BLS
    // ...

    // Step 6: Conservation check returns fee
    let fee = check_conservation(&removals, &additions)?;

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

### Usage by Callers

```rust
// Caller uses SpendResult to update state:
let result = validate_spend_bundle(&bundle, &ctx, &cfg, Some(&mut cache))?;

// Update UTXO set
for coin in &result.removals {
    utxo_set.mark_spent(coin.coin_id());
}
for coin in &result.additions {
    utxo_set.insert(coin);
}

// Record fee
total_fees += result.fee;

// Process conditions (e.g., for mempool timelocks)
for spend in &result.conditions.spends {
    if let Some(height) = spend.height_relative {
        // Track timelock for mempool eviction
    }
}
```

---

## Acceptance Criteria

- [ ] `SpendResult` contains `additions`, `removals`, `fee`, and `conditions` fields.
- [ ] `additions` contains exactly the coins created by `CREATE_COIN` conditions in the bundle.
- [ ] `removals` contains exactly the coins from each `CoinSpend` in the bundle.
- [ ] `fee` equals `sum(removal.amount) - sum(addition.amount)` and is non-negative.
- [ ] `conditions` is an `OwnedSpendBundleConditions` containing all parsed conditions and CLVM cost.
- [ ] `SpendResult` implements `Debug` and `Clone`.

---

## Implementation Notes

- **Primary file:** `src/consensus/types.rs` (struct definition), `src/consensus/validate.rs` (construction).
- **Owned types:** `SpendResult` uses `Vec<Coin>` (owned) rather than borrowed slices, so the result can outlive the input `SpendBundle`. Similarly, `OwnedSpendBundleConditions` is the owned variant of `SpendBundleConditions`.
- **No serialization required:** `SpendResult` is an in-memory type for programmatic use. It does not need `Serialize`/`Deserialize` traits, though they could be added later.
- **Thread safety:** `SpendResult` is `Send + Sync` (all fields are owned, no interior mutability), so it can be passed between threads.
- **Additions ordering:** The order of `additions` follows the execution order of coin spends in the bundle and the order of `CREATE_COIN` conditions within each spend. This order is deterministic for a given bundle.
- **Duplicate additions:** If two different spends in the same bundle create coins with the same puzzle_hash and amount, they will have different `parent_coin_info` values (since they come from different parent coins), so they will be distinct coins in `additions`.
- **Empty bundles:** A spend bundle with zero coin spends is invalid and will fail before reaching `SpendResult` construction. Thus `additions` and `removals` are never both empty.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| Additions match CREATE_COIN | Simulator test | Bundle creates 3 coins, check additions | additions.len() == 3, correct puzzle_hash/amount |
| Removals match CoinSpends | Simulator test | Bundle spends 2 coins, check removals | removals.len() == 2, correct coin data |
| Fee computed correctly | Simulator test | Spend 1000, create 900 | fee == 100 |
| Zero fee | Simulator test | Spend 500, create 500 | fee == 0 |
| Conditions populated | Simulator test | Puzzle emits AGG_SIG_ME + timelock | conditions.spends[0].agg_sig_me non-empty, height_relative set |
| Cost in conditions | Simulator test | Validate bundle, check cost | conditions.cost > 0 and <= max_block_cost |
| Clone and Debug | Unit test | Clone SpendResult, format with Debug | Both work without panic |

---

## Source Citations

- **SPEC.md Section 5.1**: SpendResult definition — "additions (CREATE_COIN), removals (spent coins), fee (inputs-outputs), conditions (OwnedSpendBundleConditions)."
- **L1 reference — generator_tools.py:54:** [`generator_tools.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/generator_tools.py#L54) — `tx_removals_and_additions()` extracts additions and removals.
- **L1 reference — mempool_item.py:42:** [`mempool_item.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/full_node/mempool_item.py#L42) — `MempoolItem.conds` stores parsed conditions.

---

## References

- [VAL-007](VAL-007.md) — Cost enforcement (cost is in conditions.cost)
- [VAL-009](VAL-009.md) — Condition validation (conditions originate here)
- [VAL-011](VAL-011.md) — Conservation check (fee is computed here)
- [BLK-009](../../block_generator/specs/BLK-009.md) — Round-trip consistency (SpendResult from block must match per-bundle)
