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 tree at the given path and key
447    pub fn for_estimated_path_key_empty_tree(
448        path: KeyInfoPath,
449        key: KeyInfo,
450        storage_flags: Option<&StorageFlags>,
451    ) -> Self {
452        let tree = match storage_flags {
453            Some(storage_flags) => {
454                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
455            }
456            None => Element::empty_tree(),
457        };
458
459        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
460    }
461
462    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
463    pub fn for_estimated_path_key_empty_sum_tree(
464        path: KeyInfoPath,
465        key: KeyInfo,
466        storage_flags: Option<&StorageFlags>,
467    ) -> Self {
468        let tree = match storage_flags {
469            Some(storage_flags) => {
470                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
471            }
472            None => Element::empty_sum_tree(),
473        };
474
475        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
476    }
477
478    /// Sets `GroveOperation` for inserting an element at the given path and key
479    pub fn insert_for_known_path_key_element(
480        path: Vec<Vec<u8>>,
481        key: Vec<u8>,
482        element: Element,
483    ) -> Self {
484        GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
485    }
486
487    /// Sets `GroveOperation` for replacement of an element at the given path and key
488    pub fn replace_for_known_path_key_element(
489        path: Vec<Vec<u8>>,
490        key: Vec<u8>,
491        element: Element,
492    ) -> Self {
493        GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
494    }
495
496    /// Sets `GroveOperation` for patching of an element at the given path and key
497    /// This is different from replacement which does not add or delete bytes
498    pub fn patch_for_known_path_key_element(
499        path: Vec<Vec<u8>>,
500        key: Vec<u8>,
501        element: Element,
502        change_in_bytes: i32,
503    ) -> Self {
504        GroveOperation(QualifiedGroveDbOp::patch_op(
505            path,
506            key,
507            element,
508            change_in_bytes,
509        ))
510    }
511
512    /// Sets `GroveOperation` for inserting an element at an unknown estimated path and key
513    pub fn insert_for_estimated_path_key_element(
514        path: KeyInfoPath,
515        key: KeyInfo,
516        element: Element,
517    ) -> Self {
518        GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
519    }
520
521    /// Sets `GroveOperation` for replacement of an element at an unknown estimated path and key
522    pub fn replace_for_estimated_path_key_element(
523        path: KeyInfoPath,
524        key: KeyInfo,
525        element: Element,
526    ) -> Self {
527        GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
528    }
529
530    /// Sets `GroveOperation` for refresh of a reference at the given path and key
531    pub fn refresh_reference_for_known_path_key_reference_info(
532        path: Vec<Vec<u8>>,
533        key: Vec<u8>,
534        reference_path_type: ReferencePathType,
535        max_reference_hop: MaxReferenceHop,
536        flags: Option<ElementFlags>,
537        trust_refresh_reference: bool,
538    ) -> Self {
539        GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
540            path,
541            key,
542            reference_path_type,
543            max_reference_hop,
544            flags,
545            trust_refresh_reference,
546        ))
547    }
548}
549
550/// A trait for getting an empty tree operation based on the tree type
551pub trait LowLevelDriveOperationTreeTypeConverter {
552    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
553    fn empty_tree_operation_for_known_path_key(
554        &self,
555        path: Vec<Vec<u8>>,
556        key: Vec<u8>,
557        storage_flags: Option<&StorageFlags>,
558    ) -> Result<LowLevelDriveOperation, Error>;
559}
560
561impl LowLevelDriveOperationTreeTypeConverter for TreeType {
562    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
563    fn empty_tree_operation_for_known_path_key(
564        &self,
565        path: Vec<Vec<u8>>,
566        key: Vec<u8>,
567        storage_flags: Option<&StorageFlags>,
568    ) -> Result<LowLevelDriveOperation, Error> {
569        let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
570        let element = match self {
571            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
572            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
573            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
574            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
575            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
576            TreeType::ProvableCountTree => {
577                Element::empty_provable_count_tree_with_flags(element_flags)
578            }
579            TreeType::ProvableCountSumTree => {
580                Element::empty_provable_count_sum_tree_with_flags(element_flags)
581            }
582            TreeType::CommitmentTree(chunk_power) => {
583                Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
584            }
585            TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
586            TreeType::BulkAppendTree(chunk_power) => {
587                Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
588            }
589            TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
590                Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
591            }
592        };
593
594        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
595            path, key, element,
596        ))
597    }
598}
599
600/// Drive cost trait
601pub trait DriveCost {
602    /// Ephemeral cost
603    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
604}
605
606impl DriveCost for OperationCost {
607    /// Return the ephemeral cost from the operation
608    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
609        let OperationCost {
610            seek_count,
611            storage_cost,
612            storage_loaded_bytes,
613            hash_node_calls,
614            sinsemilla_hash_calls,
615        } = self;
616        let epoch_cost_for_processing_credit_per_byte =
617            fee_version.storage.storage_processing_credit_per_byte;
618        let seek_cost = (*seek_count as u64)
619            .checked_mul(fee_version.storage.storage_seek_cost)
620            .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
621        let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
622            .checked_mul(epoch_cost_for_processing_credit_per_byte)
623            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
624        let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
625            .checked_mul(epoch_cost_for_processing_credit_per_byte)
626            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
627        let storage_removed_bytes_ephemeral_cost =
628            (storage_cost.removed_bytes.total_removed_bytes() as u64)
629                .checked_mul(epoch_cost_for_processing_credit_per_byte)
630                .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
631        // not accessible
632        let storage_loaded_bytes_cost = { *storage_loaded_bytes }
633            .checked_mul(fee_version.storage.storage_load_credit_per_byte)
634            .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
635
636        // There is one block per hash node call
637        let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
638        // this can't overflow
639        let hash_node_cost = blake3_total * (*hash_node_calls as u64);
640        let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
641        seek_cost
642            .checked_add(storage_added_bytes_ephemeral_cost)
643            .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
644            .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
645            .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
646            .and_then(|c| c.checked_add(hash_node_cost))
647            .and_then(|c| c.checked_add(sinsemilla_cost))
648            .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655    use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
656    use grovedb_costs::storage_cost::StorageCost;
657    use platform_version::version::fee::storage::FeeStorageVersion;
658    use platform_version::version::fee::FeeVersion;
659
660    /// Helper to get the canonical fee version used across these tests.
661    fn fee_version() -> &'static FeeVersion {
662        FeeVersion::first()
663    }
664
665    // ---------------------------------------------------------------
666    // 1. BaseOp::cost() — spot-check several opcodes
667    // ---------------------------------------------------------------
668
669    #[test]
670    fn base_op_stop_costs_zero() {
671        assert_eq!(BaseOp::Stop.cost(), 0);
672    }
673
674    #[test]
675    fn base_op_add_costs_12() {
676        assert_eq!(BaseOp::Add.cost(), 12);
677    }
678
679    #[test]
680    fn base_op_mul_costs_20() {
681        assert_eq!(BaseOp::Mul.cost(), 20);
682    }
683
684    #[test]
685    fn base_op_signextend_costs_20() {
686        assert_eq!(BaseOp::Signextend.cost(), 20);
687    }
688
689    #[test]
690    fn base_op_addmod_costs_32() {
691        assert_eq!(BaseOp::Addmod.cost(), 32);
692    }
693
694    #[test]
695    fn base_op_mulmod_costs_32() {
696        assert_eq!(BaseOp::Mulmod.cost(), 32);
697    }
698
699    #[test]
700    fn base_op_byte_costs_12() {
701        assert_eq!(BaseOp::Byte.cost(), 12);
702    }
703
704    #[test]
705    fn base_op_sub_costs_12() {
706        assert_eq!(BaseOp::Sub.cost(), 12);
707    }
708
709    #[test]
710    fn base_op_div_costs_20() {
711        assert_eq!(BaseOp::Div.cost(), 20);
712    }
713
714    #[test]
715    fn base_op_comparison_ops_all_cost_12() {
716        for op in [
717            BaseOp::Lt,
718            BaseOp::Gt,
719            BaseOp::Slt,
720            BaseOp::Sgt,
721            BaseOp::Eq,
722            BaseOp::Iszero,
723        ] {
724            assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
725        }
726    }
727
728    #[test]
729    fn base_op_bitwise_ops_all_cost_12() {
730        for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
731            assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
732        }
733    }
734
735    // ---------------------------------------------------------------
736    // 2. HashFunction — block_size / rounds / block_cost / base_cost
737    // ---------------------------------------------------------------
738
739    #[test]
740    fn hash_function_block_size_all_64() {
741        // All four hash functions currently have a 64-byte block size.
742        assert_eq!(HashFunction::Sha256.block_size(), 64);
743        assert_eq!(HashFunction::Sha256_2.block_size(), 64);
744        assert_eq!(HashFunction::Blake3.block_size(), 64);
745        assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
746    }
747
748    #[test]
749    fn hash_function_rounds() {
750        assert_eq!(HashFunction::Sha256.rounds(), 1);
751        assert_eq!(HashFunction::Sha256_2.rounds(), 2);
752        assert_eq!(HashFunction::Blake3.rounds(), 1);
753        assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
754    }
755
756    #[test]
757    fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
758        let fv = fee_version();
759        let expected = fv.hashing.sha256_per_block;
760        assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
761        assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
762        assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
763    }
764
765    #[test]
766    fn hash_function_block_cost_blake3_uses_blake3_per_block() {
767        let fv = fee_version();
768        assert_eq!(
769            HashFunction::Blake3.block_cost(fv),
770            fv.hashing.blake3_per_block
771        );
772    }
773
774    #[test]
775    fn hash_function_base_cost_sha256() {
776        let fv = fee_version();
777        assert_eq!(
778            HashFunction::Sha256.base_cost(fv),
779            fv.hashing.single_sha256_base
780        );
781    }
782
783    #[test]
784    fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
785        let fv = fee_version();
786        // Sha256_2 intentionally uses single_sha256_base (extra rounds handle the double hash).
787        assert_eq!(
788            HashFunction::Sha256_2.base_cost(fv),
789            fv.hashing.single_sha256_base
790        );
791    }
792
793    #[test]
794    fn hash_function_base_cost_blake3() {
795        let fv = fee_version();
796        assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
797    }
798
799    #[test]
800    fn hash_function_base_cost_sha256_ripe_md160() {
801        let fv = fee_version();
802        assert_eq!(
803            HashFunction::Sha256RipeMD160.base_cost(fv),
804            fv.hashing.sha256_ripe_md160_base
805        );
806    }
807
808    // ---------------------------------------------------------------
809    // 3. FunctionOp::new_with_byte_count — verify blocks/rounds calc
810    // ---------------------------------------------------------------
811
812    #[test]
813    fn function_op_new_with_byte_count_small_sha256() {
814        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
815        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
816        assert_eq!(op.rounds, 1);
817        assert_eq!(op.hash, HashFunction::Sha256);
818    }
819
820    #[test]
821    fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
822        // 64 bytes => blocks = 64/64 + 1 = 2, rounds = 2 + 1 - 1 = 2
823        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
824        assert_eq!(op.rounds, 2);
825    }
826
827    #[test]
828    fn function_op_new_with_byte_count_large_sha256() {
829        // 200 bytes => blocks = 200/64 + 1 = 3 + 1 = 4, rounds = 4 + 1 - 1 = 4
830        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
831        assert_eq!(op.rounds, 4);
832    }
833
834    #[test]
835    fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
836        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 2 - 1 = 2
837        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
838        assert_eq!(op.rounds, 2);
839    }
840
841    #[test]
842    fn function_op_new_with_byte_count_sha256_2_large() {
843        // 200 bytes => blocks = 200/64 + 1 = 4, rounds = 4 + 2 - 1 = 5
844        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
845        assert_eq!(op.rounds, 5);
846    }
847
848    #[test]
849    fn function_op_new_with_byte_count_blake3_small() {
850        // 10 bytes => blocks = 10/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
851        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
852        assert_eq!(op.rounds, 1);
853        assert_eq!(op.hash, HashFunction::Blake3);
854    }
855
856    #[test]
857    fn function_op_new_with_byte_count_blake3_large() {
858        // 500 bytes => blocks = 500/64 + 1 = 7 + 1 = 8, rounds = 8 + 1 - 1 = 8
859        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
860        assert_eq!(op.rounds, 8);
861    }
862
863    #[test]
864    fn function_op_new_with_byte_count_zero_bytes() {
865        // 0 bytes => blocks = 0/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
866        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
867        assert_eq!(op.rounds, 1);
868    }
869
870    #[test]
871    fn function_op_new_with_byte_count_sha256_ripemd160() {
872        // 20 bytes => blocks = 20/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
873        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
874        assert_eq!(op.rounds, 1);
875        assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
876    }
877
878    // ---------------------------------------------------------------
879    // 4. FunctionOp::cost — verify rounds * block_cost + base_cost
880    // ---------------------------------------------------------------
881
882    #[test]
883    fn function_op_cost_sha256_one_round() {
884        let fv = fee_version();
885        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
886        // cost = base + rounds * block_cost = 100 + 1 * 5000 = 5100
887        let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
888        assert_eq!(op.cost(fv), expected);
889    }
890
891    #[test]
892    fn function_op_cost_sha256_2_two_rounds() {
893        let fv = fee_version();
894        let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
895        // cost = base + rounds * block_cost = 100 + 2 * 5000 = 10100
896        let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
897        assert_eq!(op.cost(fv), expected);
898    }
899
900    #[test]
901    fn function_op_cost_blake3_one_round() {
902        let fv = fee_version();
903        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
904        // cost = blake3_base + 1 * blake3_per_block = 100 + 300 = 400
905        let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
906        assert_eq!(op.cost(fv), expected);
907    }
908
909    #[test]
910    fn function_op_cost_zero_rounds() {
911        let fv = fee_version();
912        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
913        // cost = blake3_base + 0 * blake3_per_block = blake3_base
914        assert_eq!(op.cost(fv), fv.hashing.blake3_base);
915    }
916
917    #[test]
918    fn function_op_cost_from_byte_count_matches_manual_calc() {
919        let fv = fee_version();
920        // 128 bytes of SHA256: blocks = 128/64 + 1 = 3, rounds = 3 + 1 - 1 = 3
921        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
922        assert_eq!(op.rounds, 3);
923        let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
924        assert_eq!(op.cost(fv), expected);
925    }
926
927    #[test]
928    fn function_op_cost_sha256_ripemd160() {
929        let fv = fee_version();
930        let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
931        let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
932        assert_eq!(op.cost(fv), expected);
933    }
934
935    #[test]
936    fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
937        let fv = fee_version();
938        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
939        // u32::MAX as u64 * sha256_per_block (5000) fits in u64 without overflow,
940        // so cost = base + rounds * block_cost, computed via saturating ops.
941        let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
942        let expected = fv
943            .hashing
944            .single_sha256_base
945            .saturating_add(expected_block_cost);
946        assert_eq!(op.cost(fv), expected);
947    }
948
949    #[test]
950    fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
951        // Construct a fee version where block_cost is large enough that
952        // u32::MAX * block_cost overflows u64, triggering saturation.
953        let mut fv = fee_version().clone();
954        fv.hashing.sha256_per_block = u64::MAX;
955        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
956        // 2 * u64::MAX saturates to u64::MAX, then base.saturating_add(u64::MAX) = u64::MAX.
957        assert_eq!(op.cost(&fv), u64::MAX);
958    }
959
960    // ---------------------------------------------------------------
961    // 5. operation_cost() — test all 4 match arms
962    // ---------------------------------------------------------------
963
964    #[test]
965    fn operation_cost_calculated_cost_operation_returns_cost() {
966        let cost = OperationCost {
967            seek_count: 3,
968            storage_cost: StorageCost {
969                added_bytes: 100,
970                replaced_bytes: 50,
971                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
972            },
973            storage_loaded_bytes: 200,
974            hash_node_calls: 5,
975            sinsemilla_hash_calls: 0,
976        };
977        let op = CalculatedCostOperation(cost.clone());
978        let result = op.operation_cost().expect("should return Ok");
979        assert_eq!(result, cost);
980    }
981
982    #[test]
983    fn operation_cost_grove_operation_returns_error() {
984        let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
985            vec![vec![1, 2, 3]],
986            vec![4, 5, 6],
987            Element::empty_tree(),
988        );
989        let result = grove_op.operation_cost();
990        assert!(result.is_err());
991        let err_msg = format!("{:?}", result.unwrap_err());
992        assert!(
993            err_msg.contains("grove operations must be executed"),
994            "unexpected error: {}",
995            err_msg
996        );
997    }
998
999    #[test]
1000    fn operation_cost_pre_calculated_fee_result_returns_error() {
1001        let fee = FeeResult {
1002            storage_fee: 100,
1003            processing_fee: 200,
1004            ..Default::default()
1005        };
1006        let op = PreCalculatedFeeResult(fee);
1007        let result = op.operation_cost();
1008        assert!(result.is_err());
1009        let err_msg = format!("{:?}", result.unwrap_err());
1010        assert!(
1011            err_msg.contains("pre calculated fees should not be requested"),
1012            "unexpected error: {}",
1013            err_msg
1014        );
1015    }
1016
1017    #[test]
1018    fn operation_cost_function_operation_returns_error() {
1019        let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1020        let result = func_op.operation_cost();
1021        assert!(result.is_err());
1022        let err_msg = format!("{:?}", result.unwrap_err());
1023        assert!(
1024            err_msg.contains("function operations should not be requested"),
1025            "unexpected error: {}",
1026            err_msg
1027        );
1028    }
1029
1030    // ---------------------------------------------------------------
1031    // 6. combine_cost_operations — filter and sum
1032    // ---------------------------------------------------------------
1033
1034    #[test]
1035    fn combine_cost_operations_sums_calculated_costs_only() {
1036        let cost1 = OperationCost {
1037            seek_count: 2,
1038            storage_cost: StorageCost {
1039                added_bytes: 10,
1040                replaced_bytes: 0,
1041                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1042            },
1043            storage_loaded_bytes: 50,
1044            hash_node_calls: 1,
1045            sinsemilla_hash_calls: 0,
1046        };
1047        let cost2 = OperationCost {
1048            seek_count: 3,
1049            storage_cost: StorageCost {
1050                added_bytes: 20,
1051                replaced_bytes: 5,
1052                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1053            },
1054            storage_loaded_bytes: 100,
1055            hash_node_calls: 2,
1056            sinsemilla_hash_calls: 1,
1057        };
1058
1059        let operations = vec![
1060            CalculatedCostOperation(cost1.clone()),
1061            // This FunctionOperation should be ignored by combine_cost_operations
1062            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1063            CalculatedCostOperation(cost2.clone()),
1064            // PreCalculatedFeeResult should also be ignored
1065            PreCalculatedFeeResult(FeeResult::default()),
1066        ];
1067
1068        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1069        assert_eq!(combined.seek_count, 2 + 3);
1070        assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1071        assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1072        assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1073        assert_eq!(combined.hash_node_calls, 1 + 2);
1074        assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1075    }
1076
1077    #[test]
1078    fn combine_cost_operations_empty_list_returns_default() {
1079        let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1080        assert_eq!(combined, OperationCost::default());
1081    }
1082
1083    #[test]
1084    fn combine_cost_operations_no_calculated_costs_returns_default() {
1085        let operations = vec![
1086            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1087            PreCalculatedFeeResult(FeeResult {
1088                processing_fee: 999,
1089                ..Default::default()
1090            }),
1091        ];
1092        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1093        assert_eq!(combined, OperationCost::default());
1094    }
1095
1096    // ---------------------------------------------------------------
1097    // 7. grovedb_operations_batch / _consume / _consume_with_leftovers
1098    // ---------------------------------------------------------------
1099
1100    /// Helper: creates a GroveOperation variant (insert_or_replace).
1101    fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1102        LowLevelDriveOperation::insert_for_known_path_key_element(
1103            vec![vec![0]],
1104            vec![key_byte],
1105            Element::new_item(vec![key_byte]),
1106        )
1107    }
1108
1109    fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1110        vec![
1111            make_grove_op(1),
1112            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1113            make_grove_op(2),
1114            CalculatedCostOperation(OperationCost::default()),
1115            make_grove_op(3),
1116        ]
1117    }
1118
1119    #[test]
1120    fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1121        let ops = make_mixed_ops();
1122        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1123        assert_eq!(batch.len(), 3);
1124    }
1125
1126    #[test]
1127    fn grovedb_operations_batch_empty_input() {
1128        let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1129        assert!(batch.is_empty());
1130    }
1131
1132    #[test]
1133    fn grovedb_operations_batch_no_grove_ops() {
1134        let ops = vec![
1135            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1136            CalculatedCostOperation(OperationCost::default()),
1137        ];
1138        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1139        assert!(batch.is_empty());
1140    }
1141
1142    #[test]
1143    fn grovedb_operations_batch_consume_filters_grove_ops() {
1144        let ops = make_mixed_ops();
1145        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1146        assert_eq!(batch.len(), 3);
1147    }
1148
1149    #[test]
1150    fn grovedb_operations_batch_consume_empty_input() {
1151        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1152        assert!(batch.is_empty());
1153    }
1154
1155    #[test]
1156    fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1157        let ops = make_mixed_ops();
1158        let (batch, leftovers) =
1159            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1160        assert_eq!(batch.len(), 3);
1161        assert_eq!(leftovers.len(), 2);
1162
1163        // Verify leftovers contain the non-grove operations.
1164        for leftover in &leftovers {
1165            assert!(
1166                !matches!(leftover, GroveOperation(_)),
1167                "leftovers should not contain GroveOperation variants"
1168            );
1169        }
1170    }
1171
1172    #[test]
1173    fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1174        let ops = vec![make_grove_op(10), make_grove_op(20)];
1175        let (batch, leftovers) =
1176            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1177        assert_eq!(batch.len(), 2);
1178        assert!(leftovers.is_empty());
1179    }
1180
1181    #[test]
1182    fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1183        let ops = vec![
1184            CalculatedCostOperation(OperationCost::default()),
1185            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1186        ];
1187        let (batch, leftovers) =
1188            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1189        assert!(batch.is_empty());
1190        assert_eq!(leftovers.len(), 2);
1191    }
1192
1193    #[test]
1194    fn grovedb_operations_batch_consume_with_leftovers_empty() {
1195        let (batch, leftovers) =
1196            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1197        assert!(batch.is_empty());
1198        assert!(leftovers.is_empty());
1199    }
1200
1201    // ---------------------------------------------------------------
1202    // 8. DriveCost::ephemeral_cost — various scenarios
1203    // ---------------------------------------------------------------
1204
1205    #[test]
1206    fn ephemeral_cost_zero_operation() {
1207        let fv = fee_version();
1208        let cost = OperationCost::default();
1209        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1210        assert_eq!(result, 0);
1211    }
1212
1213    #[test]
1214    fn ephemeral_cost_seek_only() {
1215        let fv = fee_version();
1216        let cost = OperationCost {
1217            seek_count: 5,
1218            storage_cost: StorageCost::default(),
1219            storage_loaded_bytes: 0,
1220            hash_node_calls: 0,
1221            sinsemilla_hash_calls: 0,
1222        };
1223        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1224        let expected = 5u64 * fv.storage.storage_seek_cost;
1225        assert_eq!(result, expected);
1226    }
1227
1228    #[test]
1229    fn ephemeral_cost_storage_added_bytes() {
1230        let fv = fee_version();
1231        let cost = OperationCost {
1232            seek_count: 0,
1233            storage_cost: StorageCost {
1234                added_bytes: 100,
1235                replaced_bytes: 0,
1236                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1237            },
1238            storage_loaded_bytes: 0,
1239            hash_node_calls: 0,
1240            sinsemilla_hash_calls: 0,
1241        };
1242        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1243        let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1244        assert_eq!(result, expected);
1245    }
1246
1247    #[test]
1248    fn ephemeral_cost_storage_replaced_bytes() {
1249        let fv = fee_version();
1250        let cost = OperationCost {
1251            seek_count: 0,
1252            storage_cost: StorageCost {
1253                added_bytes: 0,
1254                replaced_bytes: 50,
1255                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1256            },
1257            storage_loaded_bytes: 0,
1258            hash_node_calls: 0,
1259            sinsemilla_hash_calls: 0,
1260        };
1261        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1262        let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1263        assert_eq!(result, expected);
1264    }
1265
1266    #[test]
1267    fn ephemeral_cost_storage_removed_bytes_basic() {
1268        let fv = fee_version();
1269        let cost = OperationCost {
1270            seek_count: 0,
1271            storage_cost: StorageCost {
1272                added_bytes: 0,
1273                replaced_bytes: 0,
1274                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1275            },
1276            storage_loaded_bytes: 0,
1277            hash_node_calls: 0,
1278            sinsemilla_hash_calls: 0,
1279        };
1280        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1281        let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1282        assert_eq!(result, expected);
1283    }
1284
1285    #[test]
1286    fn ephemeral_cost_loaded_bytes() {
1287        let fv = fee_version();
1288        let cost = OperationCost {
1289            seek_count: 0,
1290            storage_cost: StorageCost::default(),
1291            storage_loaded_bytes: 300,
1292            hash_node_calls: 0,
1293            sinsemilla_hash_calls: 0,
1294        };
1295        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1296        let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1297        assert_eq!(result, expected);
1298    }
1299
1300    #[test]
1301    fn ephemeral_cost_hash_node_calls() {
1302        let fv = fee_version();
1303        let cost = OperationCost {
1304            seek_count: 0,
1305            storage_cost: StorageCost::default(),
1306            storage_loaded_bytes: 0,
1307            hash_node_calls: 10,
1308            sinsemilla_hash_calls: 0,
1309        };
1310        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1311        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1312        let expected = blake3_total * 10;
1313        assert_eq!(result, expected);
1314    }
1315
1316    #[test]
1317    fn ephemeral_cost_sinsemilla_hash_calls() {
1318        let fv = fee_version();
1319        let cost = OperationCost {
1320            seek_count: 0,
1321            storage_cost: StorageCost::default(),
1322            storage_loaded_bytes: 0,
1323            hash_node_calls: 0,
1324            sinsemilla_hash_calls: 3,
1325        };
1326        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1327        let expected = fv.hashing.sinsemilla_base * 3;
1328        assert_eq!(result, expected);
1329    }
1330
1331    #[test]
1332    fn ephemeral_cost_all_components_combined() {
1333        let fv = fee_version();
1334        let cost = OperationCost {
1335            seek_count: 2,
1336            storage_cost: StorageCost {
1337                added_bytes: 10,
1338                replaced_bytes: 20,
1339                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1340            },
1341            storage_loaded_bytes: 40,
1342            hash_node_calls: 5,
1343            sinsemilla_hash_calls: 1,
1344        };
1345        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1346
1347        let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1348        let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1349        let added_cost = 10u64 * processing_per_byte;
1350        let replaced_cost = 20u64 * processing_per_byte;
1351        let removed_cost = 30u64 * processing_per_byte;
1352        let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1353        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1354        let hash_cost = blake3_total * 5;
1355        let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1356
1357        let expected = seek_cost
1358            + added_cost
1359            + replaced_cost
1360            + loaded_cost
1361            + removed_cost
1362            + hash_cost
1363            + sinsemilla_cost;
1364        assert_eq!(result, expected);
1365    }
1366
1367    #[test]
1368    fn ephemeral_cost_overflow_seek_cost() {
1369        let fv = &FeeVersion {
1370            storage: FeeStorageVersion {
1371                storage_seek_cost: u64::MAX,
1372                ..fee_version().storage.clone()
1373            },
1374            ..fee_version().clone()
1375        };
1376        let cost = OperationCost {
1377            seek_count: 2, // 2 * u64::MAX overflows
1378            storage_cost: StorageCost::default(),
1379            storage_loaded_bytes: 0,
1380            hash_node_calls: 0,
1381            sinsemilla_hash_calls: 0,
1382        };
1383        let result = cost.ephemeral_cost(fv);
1384        assert!(result.is_err(), "expected overflow error for seek cost");
1385    }
1386
1387    #[test]
1388    fn ephemeral_cost_overflow_storage_written_bytes() {
1389        let fv = &FeeVersion {
1390            storage: FeeStorageVersion {
1391                storage_processing_credit_per_byte: u64::MAX,
1392                ..fee_version().storage.clone()
1393            },
1394            ..fee_version().clone()
1395        };
1396        let cost = OperationCost {
1397            seek_count: 0,
1398            storage_cost: StorageCost {
1399                added_bytes: 2, // 2 * u64::MAX overflows
1400                replaced_bytes: 0,
1401                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1402            },
1403            storage_loaded_bytes: 0,
1404            hash_node_calls: 0,
1405            sinsemilla_hash_calls: 0,
1406        };
1407        let result = cost.ephemeral_cost(fv);
1408        assert!(
1409            result.is_err(),
1410            "expected overflow error for storage written bytes"
1411        );
1412    }
1413
1414    #[test]
1415    fn ephemeral_cost_overflow_loaded_bytes() {
1416        let fv = &FeeVersion {
1417            storage: FeeStorageVersion {
1418                storage_load_credit_per_byte: u64::MAX,
1419                ..fee_version().storage.clone()
1420            },
1421            ..fee_version().clone()
1422        };
1423        let cost = OperationCost {
1424            seek_count: 0,
1425            storage_cost: StorageCost::default(),
1426            storage_loaded_bytes: 2, // 2 * u64::MAX overflows
1427            hash_node_calls: 0,
1428            sinsemilla_hash_calls: 0,
1429        };
1430        let result = cost.ephemeral_cost(fv);
1431        assert!(
1432            result.is_err(),
1433            "expected overflow error for loaded bytes cost"
1434        );
1435    }
1436
1437    #[test]
1438    fn ephemeral_cost_overflow_in_addition_chain() {
1439        // Use values that individually do not overflow but whose sum does.
1440        let fv = fee_version();
1441        let cost = OperationCost {
1442            seek_count: u32::MAX,
1443            storage_cost: StorageCost {
1444                added_bytes: u32::MAX,
1445                replaced_bytes: u32::MAX,
1446                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1447            },
1448            storage_loaded_bytes: u64::MAX,
1449            hash_node_calls: u32::MAX,
1450            sinsemilla_hash_calls: u32::MAX,
1451        };
1452        let result = cost.ephemeral_cost(fv);
1453        assert!(
1454            result.is_err(),
1455            "expected overflow error when summing large components"
1456        );
1457    }
1458}