Skip to main content

drive/fees/
op.rs

1use crate::util::batch::GroveDbOpBatch;
2use grovedb_costs::storage_cost::removal::Identifier;
3use grovedb_costs::storage_cost::removal::StorageRemovedBytes::{
4    BasicStorageRemoval, NoStorageRemoval, SectionedStorageRemoval,
5};
6use std::collections::BTreeMap;
7
8use enum_map::Enum;
9use grovedb::batch::key_info::KeyInfo;
10use grovedb::batch::KeyInfoPath;
11use grovedb::element::reference_path::ReferencePathType;
12use grovedb::element::MaxReferenceHop;
13use grovedb::{batch::QualifiedGroveDbOp, Element, ElementFlags, TreeType};
14use grovedb_costs::OperationCost;
15use itertools::Itertools;
16
17use crate::error::drive::DriveError;
18use crate::error::Error;
19use crate::fees::get_overflow_error;
20use crate::fees::op::LowLevelDriveOperation::{
21    CalculatedCostOperation, FunctionOperation, GroveOperation, PreCalculatedFeeResult,
22};
23use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods;
24use crate::util::storage_flags::StorageFlags;
25use dpp::block::epoch::Epoch;
26use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
27use dpp::fee::fee_result::refunds::FeeRefunds;
28use dpp::fee::fee_result::FeeResult;
29use dpp::fee::Credits;
30use platform_version::version::fee::FeeVersion;
31
32/// Base ops
33#[derive(Debug, Enum)]
34pub enum BaseOp {
35    /// Stop
36    Stop,
37    /// Add
38    Add,
39    /// Multiply
40    Mul,
41    /// Subtract
42    Sub,
43    /// Divide
44    Div,
45    /// Sdiv
46    Sdiv,
47    /// Modulo
48    Mod,
49    /// Smod
50    Smod,
51    /// Addmod
52    Addmod,
53    /// Mulmod
54    Mulmod,
55    /// Signextend
56    Signextend,
57    /// Less than
58    Lt,
59    /// Greater than
60    Gt,
61    /// Slt
62    Slt,
63    /// Sgt
64    Sgt,
65    /// Equals
66    Eq,
67    /// Is zero
68    Iszero,
69    /// And
70    And,
71    /// Or
72    Or,
73    /// Xor
74    Xor,
75    /// Not
76    Not,
77    /// Byte
78    Byte,
79}
80
81impl BaseOp {
82    /// Match the op and get the cost
83    pub fn cost(&self) -> u64 {
84        match self {
85            BaseOp::Stop => 0,
86            BaseOp::Add => 12,
87            BaseOp::Mul => 20,
88            BaseOp::Sub => 12,
89            BaseOp::Div => 20,
90            BaseOp::Sdiv => 20,
91            BaseOp::Mod => 20,
92            BaseOp::Smod => 20,
93            BaseOp::Addmod => 32,
94            BaseOp::Mulmod => 32,
95            BaseOp::Signextend => 20,
96            BaseOp::Lt => 12,
97            BaseOp::Gt => 12,
98            BaseOp::Slt => 12,
99            BaseOp::Sgt => 12,
100            BaseOp::Eq => 12,
101            BaseOp::Iszero => 12,
102            BaseOp::And => 12,
103            BaseOp::Or => 12,
104            BaseOp::Xor => 12,
105            BaseOp::Not => 12,
106            BaseOp::Byte => 12,
107        }
108    }
109}
110
111/// Supported Hash Functions
112#[derive(Debug, Enum, PartialEq, Eq)]
113pub enum HashFunction {
114    /// Used for crypto addresses
115    Sha256RipeMD160,
116    /// Single Sha256
117    Sha256,
118    /// Double Sha256
119    Sha256_2,
120    /// Single Blake3
121    Blake3,
122}
123
124impl HashFunction {
125    fn block_size(&self) -> u16 {
126        match self {
127            HashFunction::Sha256 => 64,
128            HashFunction::Sha256_2 => 64,
129            HashFunction::Blake3 => 64,
130            HashFunction::Sha256RipeMD160 => 64,
131        }
132    }
133
134    fn rounds(&self) -> u16 {
135        match self {
136            HashFunction::Sha256 => 1,
137            HashFunction::Sha256_2 => 2,
138            HashFunction::Blake3 => 1,
139            HashFunction::Sha256RipeMD160 => 1,
140        }
141    }
142
143    fn block_cost(&self, fee_version: &FeeVersion) -> u64 {
144        match self {
145            HashFunction::Sha256 => fee_version.hashing.sha256_per_block,
146            HashFunction::Sha256_2 => fee_version.hashing.sha256_per_block,
147            HashFunction::Blake3 => fee_version.hashing.blake3_per_block,
148            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_per_block,
149        }
150    }
151
152    fn base_cost(&self, fee_version: &FeeVersion) -> u64 {
153        match self {
154            HashFunction::Sha256 => fee_version.hashing.single_sha256_base,
155            // It's normal that the base cost for a sha256 will have a single sha256 base
156            // But it has an extra block
157            HashFunction::Sha256_2 => fee_version.hashing.single_sha256_base,
158            HashFunction::Blake3 => fee_version.hashing.blake3_base,
159            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_ripe_md160_base,
160        }
161    }
162}
163
164/// A Hash Function Operation
165#[derive(Debug, PartialEq, Eq)]
166pub struct FunctionOp {
167    /// hash
168    pub(crate) hash: HashFunction,
169    /// rounds
170    pub(crate) rounds: u32,
171}
172
173impl FunctionOp {
174    /// The cost of the function
175    fn cost(&self, fee_version: &FeeVersion) -> Credits {
176        let block_cost = (self.rounds as u64).saturating_mul(self.hash.block_cost(fee_version));
177        self.hash.base_cost(fee_version).saturating_add(block_cost)
178    }
179
180    /// Create a new function operation with the following hash knowing the rounds it will take
181    /// in advance
182    pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self {
183        FunctionOp { hash, rounds }
184    }
185
186    /// Create a new function operation with the following hash knowing the number of bytes
187    /// it will hash
188    pub fn new_with_byte_count(hash: HashFunction, byte_count: u16) -> Self {
189        let blocks = byte_count / hash.block_size() + 1;
190        let rounds = blocks + hash.rounds() - 1;
191        FunctionOp {
192            hash,
193            rounds: rounds as u32,
194        }
195    }
196}
197
198/// Drive operation
199#[derive(Debug, Eq, PartialEq)]
200pub enum LowLevelDriveOperation {
201    /// Grove operation
202    GroveOperation(QualifiedGroveDbOp),
203    /// A drive operation
204    FunctionOperation(FunctionOp),
205    /// Calculated cost operation
206    CalculatedCostOperation(OperationCost),
207    /// Pre Calculated Fee Result
208    PreCalculatedFeeResult(FeeResult),
209}
210
211impl LowLevelDriveOperation {
212    /// Returns a list of the costs of the Drive operations.
213    /// Should only be used by Calculate fee
214    pub fn consume_to_fees_v0(
215        drive_operations: Vec<LowLevelDriveOperation>,
216        epoch: &Epoch,
217        epochs_per_era: u16,
218        fee_version: &FeeVersion,
219        previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
220    ) -> Result<Vec<FeeResult>, Error> {
221        drive_operations
222            .into_iter()
223            .map(|operation| match operation {
224                PreCalculatedFeeResult(f) => Ok(f),
225                FunctionOperation(op) => Ok(FeeResult {
226                    processing_fee: op.cost(fee_version),
227                    ..Default::default()
228                }),
229                _ => {
230                    let cost = operation.operation_cost()?;
231                    // There is no need for a checked multiply here because added bytes are u64 and
232                    // storage disk usage credit per byte should never be high enough to cause an overflow
233                    let storage_fee = cost.storage_cost.added_bytes as u64 * fee_version.storage.storage_disk_usage_credit_per_byte;
234                    let processing_fee = cost.ephemeral_cost(fee_version)?;
235                    let (fee_refunds, removed_bytes_from_system) =
236                        match cost.storage_cost.removed_bytes {
237                            NoStorageRemoval => (FeeRefunds::default(), 0),
238                            BasicStorageRemoval(amount) => {
239                                // this is not always considered an error
240                                (FeeRefunds::default(), amount)
241                            }
242                            SectionedStorageRemoval(mut removal_per_epoch_by_identifier) => {
243
244                                let system_amount = removal_per_epoch_by_identifier
245                                    .remove(&Identifier::default())
246                                    .map_or(0, |a| a.values().sum());
247                                if fee_version.fee_version_number == 1 {
248                                    (
249                                        FeeRefunds::from_storage_removal(
250                                            removal_per_epoch_by_identifier,
251                                            epoch.index,
252                                            epochs_per_era,
253                                            &BTreeMap::default(),
254                                        )?,
255                                        system_amount,
256                                    )
257                                } else {
258                                    let previous_fee_versions = previous_fee_versions.ok_or(Error::Drive(DriveError::CorruptedCodeExecution("expected previous epoch index fee versions to be able to offer refunds")))?;
259                                    (
260                                        FeeRefunds::from_storage_removal(
261                                            removal_per_epoch_by_identifier,
262                                            epoch.index,
263                                            epochs_per_era,
264                                            previous_fee_versions,
265                                        )?,
266                                        system_amount,
267                                    )
268                                }
269                            }
270                        };
271                    Ok(FeeResult {
272                        storage_fee,
273                        processing_fee,
274                        fee_refunds,
275                        removed_bytes_from_system,
276                    })
277                }
278            })
279            .collect()
280    }
281
282    /// Returns the cost of this operation
283    pub fn operation_cost(self) -> Result<OperationCost, Error> {
284        match self {
285            GroveOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
286                "grove operations must be executed, not directly transformed to costs",
287            ))),
288            CalculatedCostOperation(c) => Ok(c),
289            PreCalculatedFeeResult(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
290                "pre calculated fees should not be requested by operation costs",
291            ))),
292            FunctionOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
293                "function operations should not be requested by operation costs",
294            ))),
295        }
296    }
297
298    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
299    pub fn combine_cost_operations(operations: &[LowLevelDriveOperation]) -> OperationCost {
300        let mut cost = OperationCost::default();
301        operations.iter().for_each(|op| {
302            if let CalculatedCostOperation(operation_cost) = op {
303                cost += operation_cost.clone()
304            }
305        });
306        cost
307    }
308
309    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
310    pub fn grovedb_operations_batch(
311        insert_operations: &[LowLevelDriveOperation],
312    ) -> GroveDbOpBatch {
313        let operations = insert_operations
314            .iter()
315            .filter_map(|op| match op {
316                GroveOperation(grovedb_op) => Some(grovedb_op.clone()),
317                _ => None,
318            })
319            .collect();
320        GroveDbOpBatch::from_operations(operations)
321    }
322
323    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
324    pub fn grovedb_operations_batch_consume(
325        insert_operations: Vec<LowLevelDriveOperation>,
326    ) -> GroveDbOpBatch {
327        let operations = insert_operations
328            .into_iter()
329            .filter_map(|op| match op {
330                GroveOperation(grovedb_op) => Some(grovedb_op),
331                _ => None,
332            })
333            .collect();
334        GroveDbOpBatch::from_operations(operations)
335    }
336
337    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
338    pub fn grovedb_operations_batch_consume_with_leftovers(
339        insert_operations: Vec<LowLevelDriveOperation>,
340    ) -> (GroveDbOpBatch, Vec<LowLevelDriveOperation>) {
341        let (grove_operations, other_operations): (Vec<_>, Vec<_>) =
342            insert_operations.into_iter().partition_map(|op| match op {
343                GroveOperation(grovedb_op) => itertools::Either::Left(grovedb_op),
344                _ => itertools::Either::Right(op),
345            });
346
347        (
348            GroveDbOpBatch::from_operations(grove_operations),
349            other_operations,
350        )
351    }
352
353    /// Filters the groveDB ops from a list of operations and collects them in a `Vec<QualifiedGroveDbOp>`.
354    pub fn grovedb_operations_consume(
355        insert_operations: Vec<LowLevelDriveOperation>,
356    ) -> Vec<QualifiedGroveDbOp> {
357        insert_operations
358            .into_iter()
359            .filter_map(|op| match op {
360                GroveOperation(grovedb_op) => Some(grovedb_op),
361                _ => None,
362            })
363            .collect()
364    }
365
366    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
367    pub fn for_known_path_key_empty_tree(
368        path: Vec<Vec<u8>>,
369        key: Vec<u8>,
370        storage_flags: Option<&StorageFlags>,
371    ) -> Self {
372        let tree = match storage_flags {
373            Some(storage_flags) => {
374                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
375            }
376            None => Element::empty_tree(),
377        };
378
379        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
380    }
381
382    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
383    pub fn for_known_path_key_empty_sum_tree(
384        path: Vec<Vec<u8>>,
385        key: Vec<u8>,
386        storage_flags: Option<&StorageFlags>,
387    ) -> Self {
388        let tree = match storage_flags {
389            Some(storage_flags) => {
390                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
391            }
392            None => Element::empty_sum_tree(),
393        };
394
395        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
396    }
397
398    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
399    pub fn for_known_path_key_empty_big_sum_tree(
400        path: Vec<Vec<u8>>,
401        key: Vec<u8>,
402        storage_flags: Option<&StorageFlags>,
403    ) -> Self {
404        let tree = match storage_flags {
405            Some(storage_flags) => {
406                Element::new_big_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
407            }
408            None => Element::empty_big_sum_tree(),
409        };
410
411        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
412    }
413
414    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
415    pub fn for_known_path_key_empty_count_tree(
416        path: Vec<Vec<u8>>,
417        key: Vec<u8>,
418        storage_flags: Option<&StorageFlags>,
419    ) -> Self {
420        let tree = match storage_flags {
421            Some(storage_flags) => {
422                Element::new_count_tree_with_flags(None, storage_flags.to_some_element_flags())
423            }
424            None => Element::empty_count_tree(),
425        };
426
427        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
428    }
429
430    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
431    pub fn for_known_path_key_empty_count_sum_tree(
432        path: Vec<Vec<u8>>,
433        key: Vec<u8>,
434        storage_flags: Option<&StorageFlags>,
435    ) -> Self {
436        let tree = match storage_flags {
437            Some(storage_flags) => {
438                Element::new_count_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
439            }
440            None => Element::new_count_sum_tree(None),
441        };
442
443        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
444    }
445
446    /// Sets `GroveOperation` for inserting an empty `NormalTree` wrapped in
447    /// `Element::NonCounted` at the given path and key. The wrapper makes
448    /// the inserted subtree contribute 0 to a parent count tree's aggregate
449    /// (per grovedb #654). Used by the index-walker for sibling continuations
450    /// inside a `range_countable` value tree, so e.g. a compound `byColorShape`
451    /// continuation under a `byColor` value tree (which is a `CountTree`)
452    /// doesn't pollute the byColor count.
453    pub fn for_known_path_key_empty_non_counted_normal_tree(
454        path: Vec<Vec<u8>>,
455        key: Vec<u8>,
456        storage_flags: Option<&StorageFlags>,
457    ) -> Self {
458        Self::for_known_path_key_empty_non_counted_tree(
459            path,
460            key,
461            TreeType::NormalTree,
462            storage_flags,
463        )
464        .expect("NormalTree NonCounted wrapping never fails")
465    }
466
467    /// Sets `GroveOperation` for inserting an empty tree of the given
468    /// `tree_type` wrapped in `Element::NonCounted`. The wrapper makes the
469    /// inserted subtree contribute 0 to a parent count tree's aggregate
470    /// count (per grovedb #654), regardless of the inner tree variant.
471    ///
472    /// Used by the index walker for sibling continuations inside a
473    /// `range_countable` value tree (a `CountTree`). Most continuations are
474    /// plain `NormalTree`, but in nested-`range_countable` cases (e.g. an
475    /// index `[color]` is range-countable AND a deeper compound index
476    /// `[color, size]` is also range-countable), the continuation
477    /// property-name tree at `"size"` is itself a `ProvableCountTree` and
478    /// must still contribute 0 to the parent `<c1>` `CountTree`.
479    ///
480    /// Returns an error for tree variants whose `NonCounted` wrapping
481    /// hasn't been validated end-to-end yet (currently anything outside
482    /// `NormalTree` / `CountTree` / `ProvableCountTree`).
483    pub fn for_known_path_key_empty_non_counted_tree(
484        path: Vec<Vec<u8>>,
485        key: Vec<u8>,
486        tree_type: TreeType,
487        storage_flags: Option<&StorageFlags>,
488    ) -> Result<Self, Error> {
489        // Per grovedb PR 670, `Element::new_non_counted` only wraps
490        // count-bearing trees — provable-count parents reject the
491        // wrapper at the merk-layer insert guard, and sum-bearing
492        // trees use dedicated `NotSummed` / `NotCountedOrSummed`
493        // wrappers (see [`Self::for_known_path_key_empty_not_summed_tree`]
494        // / [`Self::for_known_path_key_empty_not_counted_or_summed_tree`]).
495        let element_flags = storage_flags.map(|s| s.to_element_flags());
496        let inner = match tree_type {
497            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
498            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
499            TreeType::ProvableCountTree => {
500                Element::empty_provable_count_tree_with_flags(element_flags)
501            }
502            _ => {
503                return Err(Error::Drive(DriveError::NotSupported(
504                    "NonCounted-wrapping is only supported for NormalTree, CountTree, and \
505                     ProvableCountTree. For sum-bearing continuations under a sum or \
506                     count+sum parent, use `for_known_path_key_empty_not_summed_tree` or \
507                     `for_known_path_key_empty_not_counted_or_summed_tree` instead.",
508                )));
509            }
510        };
511        // Propagate the grovedb error as a typed Drive error rather
512        // than `.expect`-ing. The match above already restricts `inner`
513        // to NormalTree / CountTree / ProvableCountTree — all of which
514        // `new_non_counted` accepts at the head this PR pins
515        // (`packages/rs-drive/Cargo.toml`'s grovedb rev) — so in
516        // practice this `?` is a no-op. Keeping it as `?` means a
517        // future grovedb bump that tightens `new_non_counted`'s
518        // accepted-variant set lands a typed `Error::GroveDB` at the
519        // call site instead of a runtime panic. The `?` conversion
520        // uses `impl From<grovedb::element::error::ElementError>`
521        // defined in `crate::error::mod.rs`.
522        let tree = Element::new_non_counted(inner)?;
523        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
524            path, key, tree,
525        ))
526    }
527
528    /// Sets `GroveOperation` for inserting an empty sum-bearing tree
529    /// wrapped in `Element::NotSummed` (grovedb PR 670). The wrapper
530    /// makes the inserted subtree contribute 0 to a parent sum tree's
531    /// running sum while still allowing any count it carries to
532    /// propagate normally. Used by the index walker for continuation
533    /// property-name trees inside a `summable`-but-not-`countable`
534    /// value tree. For continuations under a count+sum parent, use
535    /// [`Self::for_known_path_key_empty_not_counted_or_summed_tree`].
536    pub fn for_known_path_key_empty_not_summed_tree(
537        path: Vec<Vec<u8>>,
538        key: Vec<u8>,
539        tree_type: TreeType,
540        storage_flags: Option<&StorageFlags>,
541    ) -> Result<Self, Error> {
542        let element_flags = storage_flags.map(|s| s.to_element_flags());
543        let inner = match tree_type {
544            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
545            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
546            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
547            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
548            TreeType::ProvableCountSumTree => {
549                Element::empty_provable_count_sum_tree_with_flags(element_flags)
550            }
551            TreeType::ProvableCountProvableSumTree => {
552                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
553            }
554            _ => {
555                return Err(Error::Drive(DriveError::NotSupported(
556                    "NotSummed-wrapping is only supported for the six sum-bearing tree \
557                     variants (SumTree, BigSumTree, ProvableSumTree, CountSumTree, \
558                     ProvableCountSumTree, ProvableCountProvableSumTree).",
559                )));
560            }
561        };
562        let tree = Element::new_not_summed(inner).map_err(|_| {
563            Error::Drive(DriveError::NotSupported(
564                "Element::new_not_summed rejected the inner tree (unreachable given the \
565                 match above).",
566            ))
567        })?;
568        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
569            path, key, tree,
570        ))
571    }
572
573    /// Sets `GroveOperation` for inserting an empty inner tree wrapped
574    /// in the wrapper variant appropriate for an `aggregating_parent_tree_type`.
575    ///
576    /// Dispatcher around the three concrete wrapper helpers
577    /// ([`Self::for_known_path_key_empty_non_counted_tree`] /
578    /// [`Self::for_known_path_key_empty_not_summed_tree`] /
579    /// [`Self::for_known_path_key_empty_not_counted_or_summed_tree`])
580    /// keyed on **the parent's** tree type — the wrapper exists to
581    /// suppress contribution to the parent's aggregate, so the parent's
582    /// kind picks the wrapper:
583    /// - Pure count parents (`CountTree` / `ProvableCountTree`) →
584    ///   `Element::NonCounted`.
585    /// - Pure sum parents (`SumTree` / `BigSumTree` / `ProvableSumTree`)
586    ///   → `Element::NotSummed`.
587    /// - Combined count+sum parents (`CountSumTree` /
588    ///   `ProvableCountSumTree` / `ProvableCountProvableSumTree`) →
589    ///   `Element::NotCountedOrSummed`.
590    /// - Non-aggregating parents (`NormalTree`, etc.) — no wrapping
591    ///   needed; caller should use
592    ///   [`crate::fees::op::LowLevelDriveOperationTreeTypeConverter::empty_tree_operation_for_known_path_key`]
593    ///   directly. This dispatcher rejects them with `NotSupported`
594    ///   so an upstream bug surfaces immediately rather than silently
595    ///   emitting an unwrapped child that pollutes a future parent.
596    ///
597    /// `inner_tree_type` is the tree variant being inserted under the
598    /// parent — typically a property-name continuation tree
599    /// (`NormalTree` / `CountTree` / `ProvableCountTree` / their
600    /// sum-bearing siblings).
601    pub fn wrap_in_non_aggregated_for_parent_tree_type(
602        path: Vec<Vec<u8>>,
603        key: Vec<u8>,
604        aggregating_parent_tree_type: TreeType,
605        inner_tree_type: TreeType,
606        storage_flags: Option<&StorageFlags>,
607    ) -> Result<Self, Error> {
608        match aggregating_parent_tree_type {
609            // Count-only parents — wrap so the inner contributes 0 to
610            // the parent's count. The inner can be plain or itself
611            // count-bearing; the helper validates accepted variants.
612            TreeType::CountTree | TreeType::ProvableCountTree => {
613                Self::for_known_path_key_empty_non_counted_tree(
614                    path,
615                    key,
616                    inner_tree_type,
617                    storage_flags,
618                )
619            }
620            // Sum-only parents — wrap so the inner contributes 0 to
621            // the parent's sum. Inner must be sum-bearing (see
622            // `for_known_path_key_empty_not_summed_tree`'s accepted set).
623            TreeType::SumTree | TreeType::BigSumTree | TreeType::ProvableSumTree => {
624                Self::for_known_path_key_empty_not_summed_tree(
625                    path,
626                    key,
627                    inner_tree_type,
628                    storage_flags,
629                )
630            }
631            // Combined count+sum parents — wrap so both axes contribute
632            // 0. Inner must be sum-bearing.
633            TreeType::CountSumTree
634            | TreeType::ProvableCountSumTree
635            | TreeType::ProvableCountProvableSumTree => {
636                Self::for_known_path_key_empty_not_counted_or_summed_tree(
637                    path,
638                    key,
639                    inner_tree_type,
640                    storage_flags,
641                )
642            }
643            _ => Err(Error::Drive(DriveError::NotSupported(
644                "wrap_in_non_aggregated_for_parent_tree_type called with a non-aggregating \
645                 parent tree type — caller should use the unwrapped \
646                 `empty_tree_operation_for_known_path_key` path instead.",
647            ))),
648        }
649    }
650
651    /// Sets `GroveOperation` for inserting an empty sum-bearing tree
652    /// wrapped in `Element::NotCountedOrSummed` (grovedb PR 670).
653    /// Suppresses BOTH count and sum propagation to the parent — used
654    /// for continuation property-name trees under a count+sum
655    /// aggregating value tree (CountSumTree / ProvableCountSumTree /
656    /// ProvableCountProvableSumTree). Same accepted inner-type set as
657    /// [`Self::for_known_path_key_empty_not_summed_tree`].
658    pub fn for_known_path_key_empty_not_counted_or_summed_tree(
659        path: Vec<Vec<u8>>,
660        key: Vec<u8>,
661        tree_type: TreeType,
662        storage_flags: Option<&StorageFlags>,
663    ) -> Result<Self, Error> {
664        let element_flags = storage_flags.map(|s| s.to_element_flags());
665        let inner = match tree_type {
666            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
667            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
668            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
669            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
670            TreeType::ProvableCountSumTree => {
671                Element::empty_provable_count_sum_tree_with_flags(element_flags)
672            }
673            TreeType::ProvableCountProvableSumTree => {
674                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
675            }
676            _ => {
677                return Err(Error::Drive(DriveError::NotSupported(
678                    "NotCountedOrSummed-wrapping is only supported for the six sum-bearing \
679                     tree variants — see `for_known_path_key_empty_not_summed_tree`.",
680                )));
681            }
682        };
683        let tree = Element::new_not_counted_or_summed(inner).map_err(|_| {
684            Error::Drive(DriveError::NotSupported(
685                "Element::new_not_counted_or_summed rejected the inner tree (unreachable \
686                 given the match above).",
687            ))
688        })?;
689        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
690            path, key, tree,
691        ))
692    }
693
694    /// Sets `GroveOperation` for inserting an empty provable count tree at the given path and key
695    pub fn for_known_path_key_empty_provable_count_tree(
696        path: Vec<Vec<u8>>,
697        key: Vec<u8>,
698        storage_flags: Option<&StorageFlags>,
699    ) -> Self {
700        let tree = match storage_flags {
701            Some(storage_flags) => Element::new_provable_count_tree_with_flags(
702                None,
703                storage_flags.to_some_element_flags(),
704            ),
705            None => Element::empty_provable_count_tree(),
706        };
707
708        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
709    }
710
711    /// Sets `GroveOperation` for inserting an empty provable sum tree at
712    /// the given path and key. The provable variant commits aggregated
713    /// sub-sums to every internal merk node, enabling O(log n)
714    /// `AggregateSumOnRange` proofs over range queries on the property
715    /// whose values feed the tree.
716    ///
717    /// Used by the index walker for property-name trees of indexes that
718    /// declare `rangeSummable: true` (mirrors the count-side
719    /// [`Self::for_known_path_key_empty_provable_count_tree`]).
720    pub fn for_known_path_key_empty_provable_sum_tree(
721        path: Vec<Vec<u8>>,
722        key: Vec<u8>,
723        storage_flags: Option<&StorageFlags>,
724    ) -> Self {
725        let tree = match storage_flags {
726            Some(storage_flags) => Element::new_provable_sum_tree_with_flags(
727                None,
728                storage_flags.to_some_element_flags(),
729            ),
730            None => Element::empty_provable_sum_tree(),
731        };
732
733        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
734    }
735
736    /// Sets `GroveOperation` for inserting an empty provable
737    /// count-sum tree at the given path and key. **Pre-PR-670
738    /// variant**: per-node counts committed to every internal merk
739    /// node, but the sum is only carried at the root (not per-node).
740    /// Use this when an index declares `rangeCountable: true` plus
741    /// non-range `summable: "<prop>"` — count queries get the
742    /// `AggregateCountOnRange` benefit while sum queries return only
743    /// the root total.
744    pub fn for_known_path_key_empty_provable_count_sum_tree(
745        path: Vec<Vec<u8>>,
746        key: Vec<u8>,
747        storage_flags: Option<&StorageFlags>,
748    ) -> Self {
749        let tree = match storage_flags {
750            Some(storage_flags) => Element::new_provable_count_sum_tree_with_flags(
751                None,
752                storage_flags.to_some_element_flags(),
753            ),
754            None => Element::empty_provable_count_sum_tree(),
755        };
756
757        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
758    }
759
760    /// Sets `GroveOperation` for inserting an empty
761    /// **provable-count-provable-sum** tree (PCPS) at the given path
762    /// and key. The grovedb PR 670 newcomer: **both** per-node counts
763    /// AND per-node sums committed to every internal merk node, so a
764    /// single tree can answer both `AggregateCountOnRange`,
765    /// `AggregateSumOnRange`, AND the new
766    /// `AggregateCountAndSumOnRange` (combined) range queries.
767    ///
768    /// Used by the index walker for property-name trees of indexes
769    /// that declare BOTH `rangeCountable: true` AND `rangeSummable:
770    /// true`, and for primary-key trees that declare both at the
771    /// doctype level. The dispatch table in
772    /// [`crate::drive::document::primary_key_tree_type`]'s v1 arm
773    /// picks `TreeType::ProvableCountProvableSumTree` for these
774    /// cases.
775    pub fn for_known_path_key_empty_provable_count_provable_sum_tree(
776        path: Vec<Vec<u8>>,
777        key: Vec<u8>,
778        storage_flags: Option<&StorageFlags>,
779    ) -> Self {
780        let tree = match storage_flags {
781            Some(storage_flags) => Element::new_provable_count_provable_sum_tree_with_flags(
782                None,
783                storage_flags.to_some_element_flags(),
784            ),
785            None => Element::empty_provable_count_provable_sum_tree(),
786        };
787
788        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
789    }
790
791    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
792    pub fn for_estimated_path_key_empty_tree(
793        path: KeyInfoPath,
794        key: KeyInfo,
795        storage_flags: Option<&StorageFlags>,
796    ) -> Self {
797        let tree = match storage_flags {
798            Some(storage_flags) => {
799                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
800            }
801            None => Element::empty_tree(),
802        };
803
804        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
805    }
806
807    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
808    pub fn for_estimated_path_key_empty_sum_tree(
809        path: KeyInfoPath,
810        key: KeyInfo,
811        storage_flags: Option<&StorageFlags>,
812    ) -> Self {
813        let tree = match storage_flags {
814            Some(storage_flags) => {
815                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
816            }
817            None => Element::empty_sum_tree(),
818        };
819
820        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
821    }
822
823    /// Sets `GroveOperation` for inserting an empty count tree at the given (estimated) path and key
824    pub fn for_estimated_path_key_empty_count_tree(
825        path: KeyInfoPath,
826        key: KeyInfo,
827        storage_flags: Option<&StorageFlags>,
828    ) -> Self {
829        let tree = match storage_flags {
830            Some(storage_flags) => {
831                Element::empty_count_tree_with_flags(storage_flags.to_some_element_flags())
832            }
833            None => Element::empty_count_tree(),
834        };
835
836        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
837    }
838
839    /// Sets `GroveOperation` for inserting an empty provable count tree at the given (estimated) path and key
840    pub fn for_estimated_path_key_empty_provable_count_tree(
841        path: KeyInfoPath,
842        key: KeyInfo,
843        storage_flags: Option<&StorageFlags>,
844    ) -> Self {
845        let tree = match storage_flags {
846            Some(storage_flags) => {
847                Element::empty_provable_count_tree_with_flags(storage_flags.to_some_element_flags())
848            }
849            None => Element::empty_provable_count_tree(),
850        };
851
852        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
853    }
854
855    /// Cost-estimation analog of
856    /// [`Self::for_known_path_key_empty_provable_sum_tree`]. See its doc.
857    pub fn for_estimated_path_key_empty_provable_sum_tree(
858        path: KeyInfoPath,
859        key: KeyInfo,
860        storage_flags: Option<&StorageFlags>,
861    ) -> Self {
862        let tree = match storage_flags {
863            Some(storage_flags) => {
864                Element::empty_provable_sum_tree_with_flags(storage_flags.to_some_element_flags())
865            }
866            None => Element::empty_provable_sum_tree(),
867        };
868
869        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
870    }
871
872    /// Cost-estimation analog of
873    /// [`Self::for_known_path_key_empty_count_sum_tree`]. See its doc.
874    pub fn for_estimated_path_key_empty_count_sum_tree(
875        path: KeyInfoPath,
876        key: KeyInfo,
877        storage_flags: Option<&StorageFlags>,
878    ) -> Self {
879        let tree = match storage_flags {
880            Some(storage_flags) => {
881                Element::empty_count_sum_tree_with_flags(storage_flags.to_some_element_flags())
882            }
883            None => Element::empty_count_sum_tree(),
884        };
885
886        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
887    }
888
889    /// Cost-estimation analog of
890    /// [`Self::for_known_path_key_empty_provable_count_sum_tree`]. See its
891    /// doc.
892    pub fn for_estimated_path_key_empty_provable_count_sum_tree(
893        path: KeyInfoPath,
894        key: KeyInfo,
895        storage_flags: Option<&StorageFlags>,
896    ) -> Self {
897        let tree = match storage_flags {
898            Some(storage_flags) => Element::empty_provable_count_sum_tree_with_flags(
899                storage_flags.to_some_element_flags(),
900            ),
901            None => Element::empty_provable_count_sum_tree(),
902        };
903
904        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
905    }
906
907    /// Cost-estimation analog of
908    /// [`Self::for_known_path_key_empty_provable_count_provable_sum_tree`].
909    /// See its doc.
910    pub fn for_estimated_path_key_empty_provable_count_provable_sum_tree(
911        path: KeyInfoPath,
912        key: KeyInfo,
913        storage_flags: Option<&StorageFlags>,
914    ) -> Self {
915        let tree = match storage_flags {
916            Some(storage_flags) => Element::empty_provable_count_provable_sum_tree_with_flags(
917                storage_flags.to_some_element_flags(),
918            ),
919            None => Element::empty_provable_count_provable_sum_tree(),
920        };
921
922        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
923    }
924
925    /// Sets `GroveOperation` for inserting an element at the given path and key
926    pub fn insert_for_known_path_key_element(
927        path: Vec<Vec<u8>>,
928        key: Vec<u8>,
929        element: Element,
930    ) -> Self {
931        GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
932    }
933
934    /// Sets `GroveOperation` for replacement of an element at the given path and key
935    pub fn replace_for_known_path_key_element(
936        path: Vec<Vec<u8>>,
937        key: Vec<u8>,
938        element: Element,
939    ) -> Self {
940        GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
941    }
942
943    /// Sets `GroveOperation` for patching of an element at the given path and key
944    /// This is different from replacement which does not add or delete bytes
945    pub fn patch_for_known_path_key_element(
946        path: Vec<Vec<u8>>,
947        key: Vec<u8>,
948        element: Element,
949        change_in_bytes: i32,
950    ) -> Self {
951        GroveOperation(QualifiedGroveDbOp::patch_op(
952            path,
953            key,
954            element,
955            change_in_bytes,
956        ))
957    }
958
959    /// Sets `GroveOperation` for inserting an element at an unknown estimated path and key
960    pub fn insert_for_estimated_path_key_element(
961        path: KeyInfoPath,
962        key: KeyInfo,
963        element: Element,
964    ) -> Self {
965        GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
966    }
967
968    /// Sets `GroveOperation` for replacement of an element at an unknown estimated path and key
969    pub fn replace_for_estimated_path_key_element(
970        path: KeyInfoPath,
971        key: KeyInfo,
972        element: Element,
973    ) -> Self {
974        GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
975    }
976
977    /// Sets `GroveOperation` for refresh of a reference at the given path and key
978    pub fn refresh_reference_for_known_path_key_reference_info(
979        path: Vec<Vec<u8>>,
980        key: Vec<u8>,
981        reference_path_type: ReferencePathType,
982        max_reference_hop: MaxReferenceHop,
983        flags: Option<ElementFlags>,
984        trust_refresh_reference: bool,
985    ) -> Self {
986        GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
987            path,
988            key,
989            reference_path_type,
990            max_reference_hop,
991            flags,
992            // `non_counted: false` — Drive's index references contribute to
993            // count aggregates on `ProvableCountTree` / `CountTree` parents
994            // (and to count × sum aggregates on the dual-axis combined
995            // trees). The non-counted variant exists in grovedb for
996            // siblings-of-summable-only-trees that must not bump count
997            // aggregates; Drive never refreshes those.
998            false,
999            trust_refresh_reference,
1000        ))
1001    }
1002
1003    /// Sets `GroveOperation` for refresh of a
1004    /// [`grovedb::Element::ReferenceWithSumItem`] at the given path and
1005    /// key, **overriding** the carried sum with `sum_value`.
1006    ///
1007    /// Used by document-update paths on `summable` indexes: when the
1008    /// summed property's value changes but the index keys do not, the
1009    /// reference body stays the same but its sum contribution must be
1010    /// rewritten so ancestor `SumTree` / `ProvableCountSumTree` /
1011    /// `ProvableCountProvableSumTree` aggregates pick up the delta.
1012    ///
1013    /// Mirrors [`Self::refresh_reference_for_known_path_key_reference_info`]
1014    /// but emits a grovedb `RefreshReference` op in
1015    /// `SumItemReference*` mode instead of `PlainReference*` mode.
1016    pub fn refresh_reference_with_sum_item_for_known_path_key_reference_info(
1017        path: Vec<Vec<u8>>,
1018        key: Vec<u8>,
1019        reference_path_type: ReferencePathType,
1020        max_reference_hop: MaxReferenceHop,
1021        sum_value: i64,
1022        flags: Option<ElementFlags>,
1023        trust_refresh_reference: bool,
1024    ) -> Self {
1025        GroveOperation(QualifiedGroveDbOp::refresh_reference_with_sum_item_op(
1026            path,
1027            key,
1028            reference_path_type,
1029            max_reference_hop,
1030            sum_value,
1031            flags,
1032            // `non_counted: false` — see the count-tree rationale on the
1033            // plain-reference helper above. Same reasoning applies on the
1034            // sum side: index references always contribute to ancestor
1035            // count aggregates.
1036            false,
1037            trust_refresh_reference,
1038        ))
1039    }
1040}
1041
1042/// A trait for getting an empty tree operation based on the tree type
1043pub trait LowLevelDriveOperationTreeTypeConverter {
1044    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
1045    fn empty_tree_operation_for_known_path_key(
1046        &self,
1047        path: Vec<Vec<u8>>,
1048        key: Vec<u8>,
1049        storage_flags: Option<&StorageFlags>,
1050    ) -> Result<LowLevelDriveOperation, Error>;
1051}
1052
1053impl LowLevelDriveOperationTreeTypeConverter for TreeType {
1054    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
1055    fn empty_tree_operation_for_known_path_key(
1056        &self,
1057        path: Vec<Vec<u8>>,
1058        key: Vec<u8>,
1059        storage_flags: Option<&StorageFlags>,
1060    ) -> Result<LowLevelDriveOperation, Error> {
1061        let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
1062        let element = match self {
1063            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
1064            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
1065            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
1066            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
1067            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
1068            TreeType::ProvableCountTree => {
1069                Element::empty_provable_count_tree_with_flags(element_flags)
1070            }
1071            TreeType::ProvableCountSumTree => {
1072                Element::empty_provable_count_sum_tree_with_flags(element_flags)
1073            }
1074            TreeType::ProvableCountProvableSumTree => {
1075                Element::empty_provable_count_provable_sum_tree_with_flags(element_flags)
1076            }
1077            TreeType::ProvableSumTree => Element::empty_provable_sum_tree_with_flags(element_flags),
1078            TreeType::CommitmentTree(chunk_power) => {
1079                Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
1080            }
1081            TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
1082            TreeType::BulkAppendTree(chunk_power) => {
1083                Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
1084            }
1085            TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
1086                Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
1087            }
1088        };
1089
1090        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
1091            path, key, element,
1092        ))
1093    }
1094}
1095
1096/// Drive cost trait
1097pub trait DriveCost {
1098    /// Ephemeral cost
1099    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
1100}
1101
1102impl DriveCost for OperationCost {
1103    /// Return the ephemeral cost from the operation
1104    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
1105        let OperationCost {
1106            seek_count,
1107            storage_cost,
1108            storage_loaded_bytes,
1109            hash_node_calls,
1110            sinsemilla_hash_calls,
1111        } = self;
1112        let epoch_cost_for_processing_credit_per_byte =
1113            fee_version.storage.storage_processing_credit_per_byte;
1114        let seek_cost = (*seek_count as u64)
1115            .checked_mul(fee_version.storage.storage_seek_cost)
1116            .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
1117        let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
1118            .checked_mul(epoch_cost_for_processing_credit_per_byte)
1119            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1120        let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
1121            .checked_mul(epoch_cost_for_processing_credit_per_byte)
1122            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1123        let storage_removed_bytes_ephemeral_cost =
1124            (storage_cost.removed_bytes.total_removed_bytes() as u64)
1125                .checked_mul(epoch_cost_for_processing_credit_per_byte)
1126                .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
1127        // not accessible
1128        let storage_loaded_bytes_cost = { *storage_loaded_bytes }
1129            .checked_mul(fee_version.storage.storage_load_credit_per_byte)
1130            .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
1131
1132        // There is one block per hash node call
1133        let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
1134        // this can't overflow
1135        let hash_node_cost = blake3_total * (*hash_node_calls as u64);
1136        let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
1137        seek_cost
1138            .checked_add(storage_added_bytes_ephemeral_cost)
1139            .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
1140            .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
1141            .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
1142            .and_then(|c| c.checked_add(hash_node_cost))
1143            .and_then(|c| c.checked_add(sinsemilla_cost))
1144            .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
1145    }
1146}
1147
1148#[cfg(test)]
1149#[allow(clippy::identity_op)]
1150mod tests {
1151    use super::*;
1152    use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
1153    use grovedb_costs::storage_cost::StorageCost;
1154    use platform_version::version::fee::storage::FeeStorageVersion;
1155    use platform_version::version::fee::FeeVersion;
1156
1157    /// Helper to get the canonical fee version used across these tests.
1158    fn fee_version() -> &'static FeeVersion {
1159        FeeVersion::first()
1160    }
1161
1162    // ---------------------------------------------------------------
1163    // 1. BaseOp::cost() — spot-check several opcodes
1164    // ---------------------------------------------------------------
1165
1166    #[test]
1167    fn base_op_stop_costs_zero() {
1168        assert_eq!(BaseOp::Stop.cost(), 0);
1169    }
1170
1171    #[test]
1172    fn base_op_add_costs_12() {
1173        assert_eq!(BaseOp::Add.cost(), 12);
1174    }
1175
1176    #[test]
1177    fn base_op_mul_costs_20() {
1178        assert_eq!(BaseOp::Mul.cost(), 20);
1179    }
1180
1181    #[test]
1182    fn base_op_signextend_costs_20() {
1183        assert_eq!(BaseOp::Signextend.cost(), 20);
1184    }
1185
1186    #[test]
1187    fn base_op_addmod_costs_32() {
1188        assert_eq!(BaseOp::Addmod.cost(), 32);
1189    }
1190
1191    #[test]
1192    fn base_op_mulmod_costs_32() {
1193        assert_eq!(BaseOp::Mulmod.cost(), 32);
1194    }
1195
1196    #[test]
1197    fn base_op_byte_costs_12() {
1198        assert_eq!(BaseOp::Byte.cost(), 12);
1199    }
1200
1201    #[test]
1202    fn base_op_sub_costs_12() {
1203        assert_eq!(BaseOp::Sub.cost(), 12);
1204    }
1205
1206    #[test]
1207    fn base_op_div_costs_20() {
1208        assert_eq!(BaseOp::Div.cost(), 20);
1209    }
1210
1211    #[test]
1212    fn base_op_comparison_ops_all_cost_12() {
1213        for op in [
1214            BaseOp::Lt,
1215            BaseOp::Gt,
1216            BaseOp::Slt,
1217            BaseOp::Sgt,
1218            BaseOp::Eq,
1219            BaseOp::Iszero,
1220        ] {
1221            assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
1222        }
1223    }
1224
1225    #[test]
1226    fn base_op_bitwise_ops_all_cost_12() {
1227        for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
1228            assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
1229        }
1230    }
1231
1232    // ---------------------------------------------------------------
1233    // 2. HashFunction — block_size / rounds / block_cost / base_cost
1234    // ---------------------------------------------------------------
1235
1236    #[test]
1237    fn hash_function_block_size_all_64() {
1238        // All four hash functions currently have a 64-byte block size.
1239        assert_eq!(HashFunction::Sha256.block_size(), 64);
1240        assert_eq!(HashFunction::Sha256_2.block_size(), 64);
1241        assert_eq!(HashFunction::Blake3.block_size(), 64);
1242        assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
1243    }
1244
1245    #[test]
1246    fn hash_function_rounds() {
1247        assert_eq!(HashFunction::Sha256.rounds(), 1);
1248        assert_eq!(HashFunction::Sha256_2.rounds(), 2);
1249        assert_eq!(HashFunction::Blake3.rounds(), 1);
1250        assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
1251    }
1252
1253    #[test]
1254    fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
1255        let fv = fee_version();
1256        let expected = fv.hashing.sha256_per_block;
1257        assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
1258        assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
1259        assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
1260    }
1261
1262    #[test]
1263    fn hash_function_block_cost_blake3_uses_blake3_per_block() {
1264        let fv = fee_version();
1265        assert_eq!(
1266            HashFunction::Blake3.block_cost(fv),
1267            fv.hashing.blake3_per_block
1268        );
1269    }
1270
1271    #[test]
1272    fn hash_function_base_cost_sha256() {
1273        let fv = fee_version();
1274        assert_eq!(
1275            HashFunction::Sha256.base_cost(fv),
1276            fv.hashing.single_sha256_base
1277        );
1278    }
1279
1280    #[test]
1281    fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
1282        let fv = fee_version();
1283        // Sha256_2 intentionally uses single_sha256_base (extra rounds handle the double hash).
1284        assert_eq!(
1285            HashFunction::Sha256_2.base_cost(fv),
1286            fv.hashing.single_sha256_base
1287        );
1288    }
1289
1290    #[test]
1291    fn hash_function_base_cost_blake3() {
1292        let fv = fee_version();
1293        assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
1294    }
1295
1296    #[test]
1297    fn hash_function_base_cost_sha256_ripe_md160() {
1298        let fv = fee_version();
1299        assert_eq!(
1300            HashFunction::Sha256RipeMD160.base_cost(fv),
1301            fv.hashing.sha256_ripe_md160_base
1302        );
1303    }
1304
1305    // ---------------------------------------------------------------
1306    // 3. FunctionOp::new_with_byte_count — verify blocks/rounds calc
1307    // ---------------------------------------------------------------
1308
1309    #[test]
1310    fn function_op_new_with_byte_count_small_sha256() {
1311        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1312        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
1313        assert_eq!(op.rounds, 1);
1314        assert_eq!(op.hash, HashFunction::Sha256);
1315    }
1316
1317    #[test]
1318    fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
1319        // 64 bytes => blocks = 64/64 + 1 = 2, rounds = 2 + 1 - 1 = 2
1320        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
1321        assert_eq!(op.rounds, 2);
1322    }
1323
1324    #[test]
1325    fn function_op_new_with_byte_count_large_sha256() {
1326        // 200 bytes => blocks = 200/64 + 1 = 3 + 1 = 4, rounds = 4 + 1 - 1 = 4
1327        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
1328        assert_eq!(op.rounds, 4);
1329    }
1330
1331    #[test]
1332    fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
1333        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 2 - 1 = 2
1334        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
1335        assert_eq!(op.rounds, 2);
1336    }
1337
1338    #[test]
1339    fn function_op_new_with_byte_count_sha256_2_large() {
1340        // 200 bytes => blocks = 200/64 + 1 = 4, rounds = 4 + 2 - 1 = 5
1341        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
1342        assert_eq!(op.rounds, 5);
1343    }
1344
1345    #[test]
1346    fn function_op_new_with_byte_count_blake3_small() {
1347        // 10 bytes => blocks = 10/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1348        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
1349        assert_eq!(op.rounds, 1);
1350        assert_eq!(op.hash, HashFunction::Blake3);
1351    }
1352
1353    #[test]
1354    fn function_op_new_with_byte_count_blake3_large() {
1355        // 500 bytes => blocks = 500/64 + 1 = 7 + 1 = 8, rounds = 8 + 1 - 1 = 8
1356        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
1357        assert_eq!(op.rounds, 8);
1358    }
1359
1360    #[test]
1361    fn function_op_new_with_byte_count_zero_bytes() {
1362        // 0 bytes => blocks = 0/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1363        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
1364        assert_eq!(op.rounds, 1);
1365    }
1366
1367    #[test]
1368    fn function_op_new_with_byte_count_sha256_ripemd160() {
1369        // 20 bytes => blocks = 20/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
1370        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
1371        assert_eq!(op.rounds, 1);
1372        assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
1373    }
1374
1375    // ---------------------------------------------------------------
1376    // 4. FunctionOp::cost — verify rounds * block_cost + base_cost
1377    // ---------------------------------------------------------------
1378
1379    #[test]
1380    fn function_op_cost_sha256_one_round() {
1381        let fv = fee_version();
1382        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
1383        // cost = base + rounds * block_cost = 100 + 1 * 5000 = 5100
1384        let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
1385        assert_eq!(op.cost(fv), expected);
1386    }
1387
1388    #[test]
1389    fn function_op_cost_sha256_2_two_rounds() {
1390        let fv = fee_version();
1391        let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
1392        // cost = base + rounds * block_cost = 100 + 2 * 5000 = 10100
1393        let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
1394        assert_eq!(op.cost(fv), expected);
1395    }
1396
1397    #[test]
1398    fn function_op_cost_blake3_one_round() {
1399        let fv = fee_version();
1400        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
1401        // cost = blake3_base + 1 * blake3_per_block = 100 + 300 = 400
1402        let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
1403        assert_eq!(op.cost(fv), expected);
1404    }
1405
1406    #[test]
1407    fn function_op_cost_zero_rounds() {
1408        let fv = fee_version();
1409        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
1410        // cost = blake3_base + 0 * blake3_per_block = blake3_base
1411        assert_eq!(op.cost(fv), fv.hashing.blake3_base);
1412    }
1413
1414    #[test]
1415    fn function_op_cost_from_byte_count_matches_manual_calc() {
1416        let fv = fee_version();
1417        // 128 bytes of SHA256: blocks = 128/64 + 1 = 3, rounds = 3 + 1 - 1 = 3
1418        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
1419        assert_eq!(op.rounds, 3);
1420        let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
1421        assert_eq!(op.cost(fv), expected);
1422    }
1423
1424    #[test]
1425    fn function_op_cost_sha256_ripemd160() {
1426        let fv = fee_version();
1427        let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
1428        let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
1429        assert_eq!(op.cost(fv), expected);
1430    }
1431
1432    #[test]
1433    fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
1434        let fv = fee_version();
1435        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
1436        // u32::MAX as u64 * sha256_per_block (5000) fits in u64 without overflow,
1437        // so cost = base + rounds * block_cost, computed via saturating ops.
1438        let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
1439        let expected = fv
1440            .hashing
1441            .single_sha256_base
1442            .saturating_add(expected_block_cost);
1443        assert_eq!(op.cost(fv), expected);
1444    }
1445
1446    #[test]
1447    fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
1448        // Construct a fee version where block_cost is large enough that
1449        // u32::MAX * block_cost overflows u64, triggering saturation.
1450        let mut fv = fee_version().clone();
1451        fv.hashing.sha256_per_block = u64::MAX;
1452        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
1453        // 2 * u64::MAX saturates to u64::MAX, then base.saturating_add(u64::MAX) = u64::MAX.
1454        assert_eq!(op.cost(&fv), u64::MAX);
1455    }
1456
1457    // ---------------------------------------------------------------
1458    // 5. operation_cost() — test all 4 match arms
1459    // ---------------------------------------------------------------
1460
1461    #[test]
1462    fn operation_cost_calculated_cost_operation_returns_cost() {
1463        let cost = OperationCost {
1464            seek_count: 3,
1465            storage_cost: StorageCost {
1466                added_bytes: 100,
1467                replaced_bytes: 50,
1468                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1469            },
1470            storage_loaded_bytes: 200,
1471            hash_node_calls: 5,
1472            sinsemilla_hash_calls: 0,
1473        };
1474        let op = CalculatedCostOperation(cost.clone());
1475        let result = op.operation_cost().expect("should return Ok");
1476        assert_eq!(result, cost);
1477    }
1478
1479    #[test]
1480    fn operation_cost_grove_operation_returns_error() {
1481        let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
1482            vec![vec![1, 2, 3]],
1483            vec![4, 5, 6],
1484            Element::empty_tree(),
1485        );
1486        let result = grove_op.operation_cost();
1487        assert!(result.is_err());
1488        let err_msg = format!("{:?}", result.unwrap_err());
1489        assert!(
1490            err_msg.contains("grove operations must be executed"),
1491            "unexpected error: {}",
1492            err_msg
1493        );
1494    }
1495
1496    #[test]
1497    fn operation_cost_pre_calculated_fee_result_returns_error() {
1498        let fee = FeeResult {
1499            storage_fee: 100,
1500            processing_fee: 200,
1501            ..Default::default()
1502        };
1503        let op = PreCalculatedFeeResult(fee);
1504        let result = op.operation_cost();
1505        assert!(result.is_err());
1506        let err_msg = format!("{:?}", result.unwrap_err());
1507        assert!(
1508            err_msg.contains("pre calculated fees should not be requested"),
1509            "unexpected error: {}",
1510            err_msg
1511        );
1512    }
1513
1514    #[test]
1515    fn operation_cost_function_operation_returns_error() {
1516        let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1517        let result = func_op.operation_cost();
1518        assert!(result.is_err());
1519        let err_msg = format!("{:?}", result.unwrap_err());
1520        assert!(
1521            err_msg.contains("function operations should not be requested"),
1522            "unexpected error: {}",
1523            err_msg
1524        );
1525    }
1526
1527    // ---------------------------------------------------------------
1528    // 6. combine_cost_operations — filter and sum
1529    // ---------------------------------------------------------------
1530
1531    #[test]
1532    fn combine_cost_operations_sums_calculated_costs_only() {
1533        let cost1 = OperationCost {
1534            seek_count: 2,
1535            storage_cost: StorageCost {
1536                added_bytes: 10,
1537                replaced_bytes: 0,
1538                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1539            },
1540            storage_loaded_bytes: 50,
1541            hash_node_calls: 1,
1542            sinsemilla_hash_calls: 0,
1543        };
1544        let cost2 = OperationCost {
1545            seek_count: 3,
1546            storage_cost: StorageCost {
1547                added_bytes: 20,
1548                replaced_bytes: 5,
1549                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1550            },
1551            storage_loaded_bytes: 100,
1552            hash_node_calls: 2,
1553            sinsemilla_hash_calls: 1,
1554        };
1555
1556        let operations = vec![
1557            CalculatedCostOperation(cost1.clone()),
1558            // This FunctionOperation should be ignored by combine_cost_operations
1559            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1560            CalculatedCostOperation(cost2.clone()),
1561            // PreCalculatedFeeResult should also be ignored
1562            PreCalculatedFeeResult(FeeResult::default()),
1563        ];
1564
1565        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1566        assert_eq!(combined.seek_count, 2 + 3);
1567        assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1568        assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1569        assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1570        assert_eq!(combined.hash_node_calls, 1 + 2);
1571        assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1572    }
1573
1574    #[test]
1575    fn combine_cost_operations_empty_list_returns_default() {
1576        let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1577        assert_eq!(combined, OperationCost::default());
1578    }
1579
1580    #[test]
1581    fn combine_cost_operations_no_calculated_costs_returns_default() {
1582        let operations = vec![
1583            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1584            PreCalculatedFeeResult(FeeResult {
1585                processing_fee: 999,
1586                ..Default::default()
1587            }),
1588        ];
1589        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1590        assert_eq!(combined, OperationCost::default());
1591    }
1592
1593    // ---------------------------------------------------------------
1594    // 7. grovedb_operations_batch / _consume / _consume_with_leftovers
1595    // ---------------------------------------------------------------
1596
1597    /// Helper: creates a GroveOperation variant (insert_or_replace).
1598    fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1599        LowLevelDriveOperation::insert_for_known_path_key_element(
1600            vec![vec![0]],
1601            vec![key_byte],
1602            Element::new_item(vec![key_byte]),
1603        )
1604    }
1605
1606    fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1607        vec![
1608            make_grove_op(1),
1609            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1610            make_grove_op(2),
1611            CalculatedCostOperation(OperationCost::default()),
1612            make_grove_op(3),
1613        ]
1614    }
1615
1616    #[test]
1617    fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1618        let ops = make_mixed_ops();
1619        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1620        assert_eq!(batch.len(), 3);
1621    }
1622
1623    #[test]
1624    fn grovedb_operations_batch_empty_input() {
1625        let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1626        assert!(batch.is_empty());
1627    }
1628
1629    #[test]
1630    fn grovedb_operations_batch_no_grove_ops() {
1631        let ops = vec![
1632            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1633            CalculatedCostOperation(OperationCost::default()),
1634        ];
1635        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1636        assert!(batch.is_empty());
1637    }
1638
1639    #[test]
1640    fn grovedb_operations_batch_consume_filters_grove_ops() {
1641        let ops = make_mixed_ops();
1642        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1643        assert_eq!(batch.len(), 3);
1644    }
1645
1646    #[test]
1647    fn grovedb_operations_batch_consume_empty_input() {
1648        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1649        assert!(batch.is_empty());
1650    }
1651
1652    #[test]
1653    fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1654        let ops = make_mixed_ops();
1655        let (batch, leftovers) =
1656            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1657        assert_eq!(batch.len(), 3);
1658        assert_eq!(leftovers.len(), 2);
1659
1660        // Verify leftovers contain the non-grove operations.
1661        for leftover in &leftovers {
1662            assert!(
1663                !matches!(leftover, GroveOperation(_)),
1664                "leftovers should not contain GroveOperation variants"
1665            );
1666        }
1667    }
1668
1669    #[test]
1670    fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1671        let ops = vec![make_grove_op(10), make_grove_op(20)];
1672        let (batch, leftovers) =
1673            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1674        assert_eq!(batch.len(), 2);
1675        assert!(leftovers.is_empty());
1676    }
1677
1678    #[test]
1679    fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1680        let ops = vec![
1681            CalculatedCostOperation(OperationCost::default()),
1682            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1683        ];
1684        let (batch, leftovers) =
1685            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1686        assert!(batch.is_empty());
1687        assert_eq!(leftovers.len(), 2);
1688    }
1689
1690    #[test]
1691    fn grovedb_operations_batch_consume_with_leftovers_empty() {
1692        let (batch, leftovers) =
1693            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1694        assert!(batch.is_empty());
1695        assert!(leftovers.is_empty());
1696    }
1697
1698    // ---------------------------------------------------------------
1699    // 8. DriveCost::ephemeral_cost — various scenarios
1700    // ---------------------------------------------------------------
1701
1702    #[test]
1703    fn ephemeral_cost_zero_operation() {
1704        let fv = fee_version();
1705        let cost = OperationCost::default();
1706        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1707        assert_eq!(result, 0);
1708    }
1709
1710    #[test]
1711    fn ephemeral_cost_seek_only() {
1712        let fv = fee_version();
1713        let cost = OperationCost {
1714            seek_count: 5,
1715            storage_cost: StorageCost::default(),
1716            storage_loaded_bytes: 0,
1717            hash_node_calls: 0,
1718            sinsemilla_hash_calls: 0,
1719        };
1720        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1721        let expected = 5u64 * fv.storage.storage_seek_cost;
1722        assert_eq!(result, expected);
1723    }
1724
1725    #[test]
1726    fn ephemeral_cost_storage_added_bytes() {
1727        let fv = fee_version();
1728        let cost = OperationCost {
1729            seek_count: 0,
1730            storage_cost: StorageCost {
1731                added_bytes: 100,
1732                replaced_bytes: 0,
1733                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1734            },
1735            storage_loaded_bytes: 0,
1736            hash_node_calls: 0,
1737            sinsemilla_hash_calls: 0,
1738        };
1739        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1740        let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1741        assert_eq!(result, expected);
1742    }
1743
1744    #[test]
1745    fn ephemeral_cost_storage_replaced_bytes() {
1746        let fv = fee_version();
1747        let cost = OperationCost {
1748            seek_count: 0,
1749            storage_cost: StorageCost {
1750                added_bytes: 0,
1751                replaced_bytes: 50,
1752                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1753            },
1754            storage_loaded_bytes: 0,
1755            hash_node_calls: 0,
1756            sinsemilla_hash_calls: 0,
1757        };
1758        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1759        let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1760        assert_eq!(result, expected);
1761    }
1762
1763    #[test]
1764    fn ephemeral_cost_storage_removed_bytes_basic() {
1765        let fv = fee_version();
1766        let cost = OperationCost {
1767            seek_count: 0,
1768            storage_cost: StorageCost {
1769                added_bytes: 0,
1770                replaced_bytes: 0,
1771                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1772            },
1773            storage_loaded_bytes: 0,
1774            hash_node_calls: 0,
1775            sinsemilla_hash_calls: 0,
1776        };
1777        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1778        let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1779        assert_eq!(result, expected);
1780    }
1781
1782    #[test]
1783    fn ephemeral_cost_loaded_bytes() {
1784        let fv = fee_version();
1785        let cost = OperationCost {
1786            seek_count: 0,
1787            storage_cost: StorageCost::default(),
1788            storage_loaded_bytes: 300,
1789            hash_node_calls: 0,
1790            sinsemilla_hash_calls: 0,
1791        };
1792        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1793        let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1794        assert_eq!(result, expected);
1795    }
1796
1797    #[test]
1798    fn ephemeral_cost_hash_node_calls() {
1799        let fv = fee_version();
1800        let cost = OperationCost {
1801            seek_count: 0,
1802            storage_cost: StorageCost::default(),
1803            storage_loaded_bytes: 0,
1804            hash_node_calls: 10,
1805            sinsemilla_hash_calls: 0,
1806        };
1807        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1808        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1809        let expected = blake3_total * 10;
1810        assert_eq!(result, expected);
1811    }
1812
1813    #[test]
1814    fn ephemeral_cost_sinsemilla_hash_calls() {
1815        let fv = fee_version();
1816        let cost = OperationCost {
1817            seek_count: 0,
1818            storage_cost: StorageCost::default(),
1819            storage_loaded_bytes: 0,
1820            hash_node_calls: 0,
1821            sinsemilla_hash_calls: 3,
1822        };
1823        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1824        let expected = fv.hashing.sinsemilla_base * 3;
1825        assert_eq!(result, expected);
1826    }
1827
1828    #[test]
1829    fn ephemeral_cost_all_components_combined() {
1830        let fv = fee_version();
1831        let cost = OperationCost {
1832            seek_count: 2,
1833            storage_cost: StorageCost {
1834                added_bytes: 10,
1835                replaced_bytes: 20,
1836                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1837            },
1838            storage_loaded_bytes: 40,
1839            hash_node_calls: 5,
1840            sinsemilla_hash_calls: 1,
1841        };
1842        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1843
1844        let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1845        let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1846        let added_cost = 10u64 * processing_per_byte;
1847        let replaced_cost = 20u64 * processing_per_byte;
1848        let removed_cost = 30u64 * processing_per_byte;
1849        let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1850        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1851        let hash_cost = blake3_total * 5;
1852        let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1853
1854        let expected = seek_cost
1855            + added_cost
1856            + replaced_cost
1857            + loaded_cost
1858            + removed_cost
1859            + hash_cost
1860            + sinsemilla_cost;
1861        assert_eq!(result, expected);
1862    }
1863
1864    #[test]
1865    fn ephemeral_cost_overflow_seek_cost() {
1866        let fv = &FeeVersion {
1867            storage: FeeStorageVersion {
1868                storage_seek_cost: u64::MAX,
1869                ..fee_version().storage.clone()
1870            },
1871            ..fee_version().clone()
1872        };
1873        let cost = OperationCost {
1874            seek_count: 2, // 2 * u64::MAX overflows
1875            storage_cost: StorageCost::default(),
1876            storage_loaded_bytes: 0,
1877            hash_node_calls: 0,
1878            sinsemilla_hash_calls: 0,
1879        };
1880        let result = cost.ephemeral_cost(fv);
1881        assert!(result.is_err(), "expected overflow error for seek cost");
1882    }
1883
1884    #[test]
1885    fn ephemeral_cost_overflow_storage_written_bytes() {
1886        let fv = &FeeVersion {
1887            storage: FeeStorageVersion {
1888                storage_processing_credit_per_byte: u64::MAX,
1889                ..fee_version().storage.clone()
1890            },
1891            ..fee_version().clone()
1892        };
1893        let cost = OperationCost {
1894            seek_count: 0,
1895            storage_cost: StorageCost {
1896                added_bytes: 2, // 2 * u64::MAX overflows
1897                replaced_bytes: 0,
1898                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1899            },
1900            storage_loaded_bytes: 0,
1901            hash_node_calls: 0,
1902            sinsemilla_hash_calls: 0,
1903        };
1904        let result = cost.ephemeral_cost(fv);
1905        assert!(
1906            result.is_err(),
1907            "expected overflow error for storage written bytes"
1908        );
1909    }
1910
1911    #[test]
1912    fn ephemeral_cost_overflow_loaded_bytes() {
1913        let fv = &FeeVersion {
1914            storage: FeeStorageVersion {
1915                storage_load_credit_per_byte: u64::MAX,
1916                ..fee_version().storage.clone()
1917            },
1918            ..fee_version().clone()
1919        };
1920        let cost = OperationCost {
1921            seek_count: 0,
1922            storage_cost: StorageCost::default(),
1923            storage_loaded_bytes: 2, // 2 * u64::MAX overflows
1924            hash_node_calls: 0,
1925            sinsemilla_hash_calls: 0,
1926        };
1927        let result = cost.ephemeral_cost(fv);
1928        assert!(
1929            result.is_err(),
1930            "expected overflow error for loaded bytes cost"
1931        );
1932    }
1933
1934    /// Covers the `TreeType::ProvableSumTree` arm of
1935    /// `LowLevelDriveOperationTreeTypeConverter::empty_tree_operation_for_known_path_key`
1936    /// added by the grovedb#661 bump. Drive doesn't currently construct
1937    /// `ProvableSumTree` anywhere else, so without this test the new arm is
1938    /// uncovered.
1939    #[test]
1940    fn empty_tree_operation_for_known_path_key_provable_sum_tree() {
1941        use grovedb::batch::GroveOp;
1942
1943        let op = TreeType::ProvableSumTree
1944            .empty_tree_operation_for_known_path_key(vec![b"root".to_vec()], b"k".to_vec(), None)
1945            .expect("empty_tree_operation_for_known_path_key");
1946
1947        match op {
1948            LowLevelDriveOperation::GroveOperation(grove_op) => match grove_op.op {
1949                GroveOp::InsertOrReplace { element } => assert!(
1950                    matches!(element, Element::ProvableSumTree(..)),
1951                    "expected ProvableSumTree element, got: {:?}",
1952                    element
1953                ),
1954                other => panic!("expected GroveOp::InsertOrReplace, got: {:?}", other),
1955            },
1956            other => panic!("expected GroveOperation, got: {:?}", other),
1957        }
1958    }
1959
1960    #[test]
1961    fn ephemeral_cost_overflow_in_addition_chain() {
1962        // Use values that individually do not overflow but whose sum does.
1963        let fv = fee_version();
1964        let cost = OperationCost {
1965            seek_count: u32::MAX,
1966            storage_cost: StorageCost {
1967                added_bytes: u32::MAX,
1968                replaced_bytes: u32::MAX,
1969                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1970            },
1971            storage_loaded_bytes: u64::MAX,
1972            hash_node_calls: u32::MAX,
1973            sinsemilla_hash_calls: u32::MAX,
1974        };
1975        let result = cost.ephemeral_cost(fv);
1976        assert!(
1977            result.is_err(),
1978            "expected overflow error when summing large components"
1979        );
1980    }
1981}