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 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)]
653#[allow(clippy::identity_op)]
654mod tests {
655    use super::*;
656    use grovedb_costs::storage_cost::removal::StorageRemovedBytes;
657    use grovedb_costs::storage_cost::StorageCost;
658    use platform_version::version::fee::storage::FeeStorageVersion;
659    use platform_version::version::fee::FeeVersion;
660
661    /// Helper to get the canonical fee version used across these tests.
662    fn fee_version() -> &'static FeeVersion {
663        FeeVersion::first()
664    }
665
666    // ---------------------------------------------------------------
667    // 1. BaseOp::cost() — spot-check several opcodes
668    // ---------------------------------------------------------------
669
670    #[test]
671    fn base_op_stop_costs_zero() {
672        assert_eq!(BaseOp::Stop.cost(), 0);
673    }
674
675    #[test]
676    fn base_op_add_costs_12() {
677        assert_eq!(BaseOp::Add.cost(), 12);
678    }
679
680    #[test]
681    fn base_op_mul_costs_20() {
682        assert_eq!(BaseOp::Mul.cost(), 20);
683    }
684
685    #[test]
686    fn base_op_signextend_costs_20() {
687        assert_eq!(BaseOp::Signextend.cost(), 20);
688    }
689
690    #[test]
691    fn base_op_addmod_costs_32() {
692        assert_eq!(BaseOp::Addmod.cost(), 32);
693    }
694
695    #[test]
696    fn base_op_mulmod_costs_32() {
697        assert_eq!(BaseOp::Mulmod.cost(), 32);
698    }
699
700    #[test]
701    fn base_op_byte_costs_12() {
702        assert_eq!(BaseOp::Byte.cost(), 12);
703    }
704
705    #[test]
706    fn base_op_sub_costs_12() {
707        assert_eq!(BaseOp::Sub.cost(), 12);
708    }
709
710    #[test]
711    fn base_op_div_costs_20() {
712        assert_eq!(BaseOp::Div.cost(), 20);
713    }
714
715    #[test]
716    fn base_op_comparison_ops_all_cost_12() {
717        for op in [
718            BaseOp::Lt,
719            BaseOp::Gt,
720            BaseOp::Slt,
721            BaseOp::Sgt,
722            BaseOp::Eq,
723            BaseOp::Iszero,
724        ] {
725            assert_eq!(op.cost(), 12, "comparison op {:?} should cost 12", op);
726        }
727    }
728
729    #[test]
730    fn base_op_bitwise_ops_all_cost_12() {
731        for op in [BaseOp::And, BaseOp::Or, BaseOp::Xor, BaseOp::Not] {
732            assert_eq!(op.cost(), 12, "bitwise op {:?} should cost 12", op);
733        }
734    }
735
736    // ---------------------------------------------------------------
737    // 2. HashFunction — block_size / rounds / block_cost / base_cost
738    // ---------------------------------------------------------------
739
740    #[test]
741    fn hash_function_block_size_all_64() {
742        // All four hash functions currently have a 64-byte block size.
743        assert_eq!(HashFunction::Sha256.block_size(), 64);
744        assert_eq!(HashFunction::Sha256_2.block_size(), 64);
745        assert_eq!(HashFunction::Blake3.block_size(), 64);
746        assert_eq!(HashFunction::Sha256RipeMD160.block_size(), 64);
747    }
748
749    #[test]
750    fn hash_function_rounds() {
751        assert_eq!(HashFunction::Sha256.rounds(), 1);
752        assert_eq!(HashFunction::Sha256_2.rounds(), 2);
753        assert_eq!(HashFunction::Blake3.rounds(), 1);
754        assert_eq!(HashFunction::Sha256RipeMD160.rounds(), 1);
755    }
756
757    #[test]
758    fn hash_function_block_cost_sha256_variants_use_sha256_per_block() {
759        let fv = fee_version();
760        let expected = fv.hashing.sha256_per_block;
761        assert_eq!(HashFunction::Sha256.block_cost(fv), expected);
762        assert_eq!(HashFunction::Sha256_2.block_cost(fv), expected);
763        assert_eq!(HashFunction::Sha256RipeMD160.block_cost(fv), expected);
764    }
765
766    #[test]
767    fn hash_function_block_cost_blake3_uses_blake3_per_block() {
768        let fv = fee_version();
769        assert_eq!(
770            HashFunction::Blake3.block_cost(fv),
771            fv.hashing.blake3_per_block
772        );
773    }
774
775    #[test]
776    fn hash_function_base_cost_sha256() {
777        let fv = fee_version();
778        assert_eq!(
779            HashFunction::Sha256.base_cost(fv),
780            fv.hashing.single_sha256_base
781        );
782    }
783
784    #[test]
785    fn hash_function_base_cost_sha256_2_uses_single_sha256_base() {
786        let fv = fee_version();
787        // Sha256_2 intentionally uses single_sha256_base (extra rounds handle the double hash).
788        assert_eq!(
789            HashFunction::Sha256_2.base_cost(fv),
790            fv.hashing.single_sha256_base
791        );
792    }
793
794    #[test]
795    fn hash_function_base_cost_blake3() {
796        let fv = fee_version();
797        assert_eq!(HashFunction::Blake3.base_cost(fv), fv.hashing.blake3_base);
798    }
799
800    #[test]
801    fn hash_function_base_cost_sha256_ripe_md160() {
802        let fv = fee_version();
803        assert_eq!(
804            HashFunction::Sha256RipeMD160.base_cost(fv),
805            fv.hashing.sha256_ripe_md160_base
806        );
807    }
808
809    // ---------------------------------------------------------------
810    // 3. FunctionOp::new_with_byte_count — verify blocks/rounds calc
811    // ---------------------------------------------------------------
812
813    #[test]
814    fn function_op_new_with_byte_count_small_sha256() {
815        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
816        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 32);
817        assert_eq!(op.rounds, 1);
818        assert_eq!(op.hash, HashFunction::Sha256);
819    }
820
821    #[test]
822    fn function_op_new_with_byte_count_exact_block_boundary_sha256() {
823        // 64 bytes => blocks = 64/64 + 1 = 2, rounds = 2 + 1 - 1 = 2
824        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 64);
825        assert_eq!(op.rounds, 2);
826    }
827
828    #[test]
829    fn function_op_new_with_byte_count_large_sha256() {
830        // 200 bytes => blocks = 200/64 + 1 = 3 + 1 = 4, rounds = 4 + 1 - 1 = 4
831        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 200);
832        assert_eq!(op.rounds, 4);
833    }
834
835    #[test]
836    fn function_op_new_with_byte_count_sha256_2_has_extra_round() {
837        // 32 bytes => blocks = 32/64 + 1 = 1, rounds = 1 + 2 - 1 = 2
838        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 32);
839        assert_eq!(op.rounds, 2);
840    }
841
842    #[test]
843    fn function_op_new_with_byte_count_sha256_2_large() {
844        // 200 bytes => blocks = 200/64 + 1 = 4, rounds = 4 + 2 - 1 = 5
845        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256_2, 200);
846        assert_eq!(op.rounds, 5);
847    }
848
849    #[test]
850    fn function_op_new_with_byte_count_blake3_small() {
851        // 10 bytes => blocks = 10/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
852        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 10);
853        assert_eq!(op.rounds, 1);
854        assert_eq!(op.hash, HashFunction::Blake3);
855    }
856
857    #[test]
858    fn function_op_new_with_byte_count_blake3_large() {
859        // 500 bytes => blocks = 500/64 + 1 = 7 + 1 = 8, rounds = 8 + 1 - 1 = 8
860        let op = FunctionOp::new_with_byte_count(HashFunction::Blake3, 500);
861        assert_eq!(op.rounds, 8);
862    }
863
864    #[test]
865    fn function_op_new_with_byte_count_zero_bytes() {
866        // 0 bytes => blocks = 0/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
867        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 0);
868        assert_eq!(op.rounds, 1);
869    }
870
871    #[test]
872    fn function_op_new_with_byte_count_sha256_ripemd160() {
873        // 20 bytes => blocks = 20/64 + 1 = 1, rounds = 1 + 1 - 1 = 1
874        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256RipeMD160, 20);
875        assert_eq!(op.rounds, 1);
876        assert_eq!(op.hash, HashFunction::Sha256RipeMD160);
877    }
878
879    // ---------------------------------------------------------------
880    // 4. FunctionOp::cost — verify rounds * block_cost + base_cost
881    // ---------------------------------------------------------------
882
883    #[test]
884    fn function_op_cost_sha256_one_round() {
885        let fv = fee_version();
886        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 1);
887        // cost = base + rounds * block_cost = 100 + 1 * 5000 = 5100
888        let expected = fv.hashing.single_sha256_base + 1 * fv.hashing.sha256_per_block;
889        assert_eq!(op.cost(fv), expected);
890    }
891
892    #[test]
893    fn function_op_cost_sha256_2_two_rounds() {
894        let fv = fee_version();
895        let op = FunctionOp::new_with_round_count(HashFunction::Sha256_2, 2);
896        // cost = base + rounds * block_cost = 100 + 2 * 5000 = 10100
897        let expected = fv.hashing.single_sha256_base + 2 * fv.hashing.sha256_per_block;
898        assert_eq!(op.cost(fv), expected);
899    }
900
901    #[test]
902    fn function_op_cost_blake3_one_round() {
903        let fv = fee_version();
904        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 1);
905        // cost = blake3_base + 1 * blake3_per_block = 100 + 300 = 400
906        let expected = fv.hashing.blake3_base + 1 * fv.hashing.blake3_per_block;
907        assert_eq!(op.cost(fv), expected);
908    }
909
910    #[test]
911    fn function_op_cost_zero_rounds() {
912        let fv = fee_version();
913        let op = FunctionOp::new_with_round_count(HashFunction::Blake3, 0);
914        // cost = blake3_base + 0 * blake3_per_block = blake3_base
915        assert_eq!(op.cost(fv), fv.hashing.blake3_base);
916    }
917
918    #[test]
919    fn function_op_cost_from_byte_count_matches_manual_calc() {
920        let fv = fee_version();
921        // 128 bytes of SHA256: blocks = 128/64 + 1 = 3, rounds = 3 + 1 - 1 = 3
922        let op = FunctionOp::new_with_byte_count(HashFunction::Sha256, 128);
923        assert_eq!(op.rounds, 3);
924        let expected = fv.hashing.single_sha256_base + 3 * fv.hashing.sha256_per_block;
925        assert_eq!(op.cost(fv), expected);
926    }
927
928    #[test]
929    fn function_op_cost_sha256_ripemd160() {
930        let fv = fee_version();
931        let op = FunctionOp::new_with_round_count(HashFunction::Sha256RipeMD160, 1);
932        let expected = fv.hashing.sha256_ripe_md160_base + 1 * fv.hashing.sha256_per_block;
933        assert_eq!(op.cost(fv), expected);
934    }
935
936    #[test]
937    fn function_op_cost_saturating_mul_does_not_panic_on_large_rounds() {
938        let fv = fee_version();
939        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, u32::MAX);
940        // u32::MAX as u64 * sha256_per_block (5000) fits in u64 without overflow,
941        // so cost = base + rounds * block_cost, computed via saturating ops.
942        let expected_block_cost = (u32::MAX as u64).saturating_mul(fv.hashing.sha256_per_block);
943        let expected = fv
944            .hashing
945            .single_sha256_base
946            .saturating_add(expected_block_cost);
947        assert_eq!(op.cost(fv), expected);
948    }
949
950    #[test]
951    fn function_op_cost_saturates_to_max_with_extreme_fee_version() {
952        // Construct a fee version where block_cost is large enough that
953        // u32::MAX * block_cost overflows u64, triggering saturation.
954        let mut fv = fee_version().clone();
955        fv.hashing.sha256_per_block = u64::MAX;
956        let op = FunctionOp::new_with_round_count(HashFunction::Sha256, 2);
957        // 2 * u64::MAX saturates to u64::MAX, then base.saturating_add(u64::MAX) = u64::MAX.
958        assert_eq!(op.cost(&fv), u64::MAX);
959    }
960
961    // ---------------------------------------------------------------
962    // 5. operation_cost() — test all 4 match arms
963    // ---------------------------------------------------------------
964
965    #[test]
966    fn operation_cost_calculated_cost_operation_returns_cost() {
967        let cost = OperationCost {
968            seek_count: 3,
969            storage_cost: StorageCost {
970                added_bytes: 100,
971                replaced_bytes: 50,
972                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
973            },
974            storage_loaded_bytes: 200,
975            hash_node_calls: 5,
976            sinsemilla_hash_calls: 0,
977        };
978        let op = CalculatedCostOperation(cost.clone());
979        let result = op.operation_cost().expect("should return Ok");
980        assert_eq!(result, cost);
981    }
982
983    #[test]
984    fn operation_cost_grove_operation_returns_error() {
985        let grove_op = LowLevelDriveOperation::insert_for_known_path_key_element(
986            vec![vec![1, 2, 3]],
987            vec![4, 5, 6],
988            Element::empty_tree(),
989        );
990        let result = grove_op.operation_cost();
991        assert!(result.is_err());
992        let err_msg = format!("{:?}", result.unwrap_err());
993        assert!(
994            err_msg.contains("grove operations must be executed"),
995            "unexpected error: {}",
996            err_msg
997        );
998    }
999
1000    #[test]
1001    fn operation_cost_pre_calculated_fee_result_returns_error() {
1002        let fee = FeeResult {
1003            storage_fee: 100,
1004            processing_fee: 200,
1005            ..Default::default()
1006        };
1007        let op = PreCalculatedFeeResult(fee);
1008        let result = op.operation_cost();
1009        assert!(result.is_err());
1010        let err_msg = format!("{:?}", result.unwrap_err());
1011        assert!(
1012            err_msg.contains("pre calculated fees should not be requested"),
1013            "unexpected error: {}",
1014            err_msg
1015        );
1016    }
1017
1018    #[test]
1019    fn operation_cost_function_operation_returns_error() {
1020        let func_op = FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1));
1021        let result = func_op.operation_cost();
1022        assert!(result.is_err());
1023        let err_msg = format!("{:?}", result.unwrap_err());
1024        assert!(
1025            err_msg.contains("function operations should not be requested"),
1026            "unexpected error: {}",
1027            err_msg
1028        );
1029    }
1030
1031    // ---------------------------------------------------------------
1032    // 6. combine_cost_operations — filter and sum
1033    // ---------------------------------------------------------------
1034
1035    #[test]
1036    fn combine_cost_operations_sums_calculated_costs_only() {
1037        let cost1 = OperationCost {
1038            seek_count: 2,
1039            storage_cost: StorageCost {
1040                added_bytes: 10,
1041                replaced_bytes: 0,
1042                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1043            },
1044            storage_loaded_bytes: 50,
1045            hash_node_calls: 1,
1046            sinsemilla_hash_calls: 0,
1047        };
1048        let cost2 = OperationCost {
1049            seek_count: 3,
1050            storage_cost: StorageCost {
1051                added_bytes: 20,
1052                replaced_bytes: 5,
1053                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1054            },
1055            storage_loaded_bytes: 100,
1056            hash_node_calls: 2,
1057            sinsemilla_hash_calls: 1,
1058        };
1059
1060        let operations = vec![
1061            CalculatedCostOperation(cost1.clone()),
1062            // This FunctionOperation should be ignored by combine_cost_operations
1063            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1064            CalculatedCostOperation(cost2.clone()),
1065            // PreCalculatedFeeResult should also be ignored
1066            PreCalculatedFeeResult(FeeResult::default()),
1067        ];
1068
1069        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1070        assert_eq!(combined.seek_count, 2 + 3);
1071        assert_eq!(combined.storage_cost.added_bytes, 10 + 20);
1072        assert_eq!(combined.storage_cost.replaced_bytes, 0 + 5);
1073        assert_eq!(combined.storage_loaded_bytes, 50 + 100);
1074        assert_eq!(combined.hash_node_calls, 1 + 2);
1075        assert_eq!(combined.sinsemilla_hash_calls, 0 + 1);
1076    }
1077
1078    #[test]
1079    fn combine_cost_operations_empty_list_returns_default() {
1080        let combined = LowLevelDriveOperation::combine_cost_operations(&[]);
1081        assert_eq!(combined, OperationCost::default());
1082    }
1083
1084    #[test]
1085    fn combine_cost_operations_no_calculated_costs_returns_default() {
1086        let operations = vec![
1087            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 2)),
1088            PreCalculatedFeeResult(FeeResult {
1089                processing_fee: 999,
1090                ..Default::default()
1091            }),
1092        ];
1093        let combined = LowLevelDriveOperation::combine_cost_operations(&operations);
1094        assert_eq!(combined, OperationCost::default());
1095    }
1096
1097    // ---------------------------------------------------------------
1098    // 7. grovedb_operations_batch / _consume / _consume_with_leftovers
1099    // ---------------------------------------------------------------
1100
1101    /// Helper: creates a GroveOperation variant (insert_or_replace).
1102    fn make_grove_op(key_byte: u8) -> LowLevelDriveOperation {
1103        LowLevelDriveOperation::insert_for_known_path_key_element(
1104            vec![vec![0]],
1105            vec![key_byte],
1106            Element::new_item(vec![key_byte]),
1107        )
1108    }
1109
1110    fn make_mixed_ops() -> Vec<LowLevelDriveOperation> {
1111        vec![
1112            make_grove_op(1),
1113            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1114            make_grove_op(2),
1115            CalculatedCostOperation(OperationCost::default()),
1116            make_grove_op(3),
1117        ]
1118    }
1119
1120    #[test]
1121    fn grovedb_operations_batch_filters_grove_ops_from_ref() {
1122        let ops = make_mixed_ops();
1123        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1124        assert_eq!(batch.len(), 3);
1125    }
1126
1127    #[test]
1128    fn grovedb_operations_batch_empty_input() {
1129        let batch = LowLevelDriveOperation::grovedb_operations_batch(&[]);
1130        assert!(batch.is_empty());
1131    }
1132
1133    #[test]
1134    fn grovedb_operations_batch_no_grove_ops() {
1135        let ops = vec![
1136            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Blake3, 1)),
1137            CalculatedCostOperation(OperationCost::default()),
1138        ];
1139        let batch = LowLevelDriveOperation::grovedb_operations_batch(&ops);
1140        assert!(batch.is_empty());
1141    }
1142
1143    #[test]
1144    fn grovedb_operations_batch_consume_filters_grove_ops() {
1145        let ops = make_mixed_ops();
1146        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(ops);
1147        assert_eq!(batch.len(), 3);
1148    }
1149
1150    #[test]
1151    fn grovedb_operations_batch_consume_empty_input() {
1152        let batch = LowLevelDriveOperation::grovedb_operations_batch_consume(vec![]);
1153        assert!(batch.is_empty());
1154    }
1155
1156    #[test]
1157    fn grovedb_operations_batch_consume_with_leftovers_partitions_correctly() {
1158        let ops = make_mixed_ops();
1159        let (batch, leftovers) =
1160            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1161        assert_eq!(batch.len(), 3);
1162        assert_eq!(leftovers.len(), 2);
1163
1164        // Verify leftovers contain the non-grove operations.
1165        for leftover in &leftovers {
1166            assert!(
1167                !matches!(leftover, GroveOperation(_)),
1168                "leftovers should not contain GroveOperation variants"
1169            );
1170        }
1171    }
1172
1173    #[test]
1174    fn grovedb_operations_batch_consume_with_leftovers_all_grove() {
1175        let ops = vec![make_grove_op(10), make_grove_op(20)];
1176        let (batch, leftovers) =
1177            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1178        assert_eq!(batch.len(), 2);
1179        assert!(leftovers.is_empty());
1180    }
1181
1182    #[test]
1183    fn grovedb_operations_batch_consume_with_leftovers_no_grove() {
1184        let ops = vec![
1185            CalculatedCostOperation(OperationCost::default()),
1186            FunctionOperation(FunctionOp::new_with_round_count(HashFunction::Sha256, 1)),
1187        ];
1188        let (batch, leftovers) =
1189            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(ops);
1190        assert!(batch.is_empty());
1191        assert_eq!(leftovers.len(), 2);
1192    }
1193
1194    #[test]
1195    fn grovedb_operations_batch_consume_with_leftovers_empty() {
1196        let (batch, leftovers) =
1197            LowLevelDriveOperation::grovedb_operations_batch_consume_with_leftovers(vec![]);
1198        assert!(batch.is_empty());
1199        assert!(leftovers.is_empty());
1200    }
1201
1202    // ---------------------------------------------------------------
1203    // 8. DriveCost::ephemeral_cost — various scenarios
1204    // ---------------------------------------------------------------
1205
1206    #[test]
1207    fn ephemeral_cost_zero_operation() {
1208        let fv = fee_version();
1209        let cost = OperationCost::default();
1210        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1211        assert_eq!(result, 0);
1212    }
1213
1214    #[test]
1215    fn ephemeral_cost_seek_only() {
1216        let fv = fee_version();
1217        let cost = OperationCost {
1218            seek_count: 5,
1219            storage_cost: StorageCost::default(),
1220            storage_loaded_bytes: 0,
1221            hash_node_calls: 0,
1222            sinsemilla_hash_calls: 0,
1223        };
1224        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1225        let expected = 5u64 * fv.storage.storage_seek_cost;
1226        assert_eq!(result, expected);
1227    }
1228
1229    #[test]
1230    fn ephemeral_cost_storage_added_bytes() {
1231        let fv = fee_version();
1232        let cost = OperationCost {
1233            seek_count: 0,
1234            storage_cost: StorageCost {
1235                added_bytes: 100,
1236                replaced_bytes: 0,
1237                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1238            },
1239            storage_loaded_bytes: 0,
1240            hash_node_calls: 0,
1241            sinsemilla_hash_calls: 0,
1242        };
1243        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1244        let expected = 100u64 * fv.storage.storage_processing_credit_per_byte;
1245        assert_eq!(result, expected);
1246    }
1247
1248    #[test]
1249    fn ephemeral_cost_storage_replaced_bytes() {
1250        let fv = fee_version();
1251        let cost = OperationCost {
1252            seek_count: 0,
1253            storage_cost: StorageCost {
1254                added_bytes: 0,
1255                replaced_bytes: 50,
1256                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1257            },
1258            storage_loaded_bytes: 0,
1259            hash_node_calls: 0,
1260            sinsemilla_hash_calls: 0,
1261        };
1262        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1263        let expected = 50u64 * fv.storage.storage_processing_credit_per_byte;
1264        assert_eq!(result, expected);
1265    }
1266
1267    #[test]
1268    fn ephemeral_cost_storage_removed_bytes_basic() {
1269        let fv = fee_version();
1270        let cost = OperationCost {
1271            seek_count: 0,
1272            storage_cost: StorageCost {
1273                added_bytes: 0,
1274                replaced_bytes: 0,
1275                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(75),
1276            },
1277            storage_loaded_bytes: 0,
1278            hash_node_calls: 0,
1279            sinsemilla_hash_calls: 0,
1280        };
1281        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1282        let expected = 75u64 * fv.storage.storage_processing_credit_per_byte;
1283        assert_eq!(result, expected);
1284    }
1285
1286    #[test]
1287    fn ephemeral_cost_loaded_bytes() {
1288        let fv = fee_version();
1289        let cost = OperationCost {
1290            seek_count: 0,
1291            storage_cost: StorageCost::default(),
1292            storage_loaded_bytes: 300,
1293            hash_node_calls: 0,
1294            sinsemilla_hash_calls: 0,
1295        };
1296        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1297        let expected = 300u64 * fv.storage.storage_load_credit_per_byte;
1298        assert_eq!(result, expected);
1299    }
1300
1301    #[test]
1302    fn ephemeral_cost_hash_node_calls() {
1303        let fv = fee_version();
1304        let cost = OperationCost {
1305            seek_count: 0,
1306            storage_cost: StorageCost::default(),
1307            storage_loaded_bytes: 0,
1308            hash_node_calls: 10,
1309            sinsemilla_hash_calls: 0,
1310        };
1311        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1312        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1313        let expected = blake3_total * 10;
1314        assert_eq!(result, expected);
1315    }
1316
1317    #[test]
1318    fn ephemeral_cost_sinsemilla_hash_calls() {
1319        let fv = fee_version();
1320        let cost = OperationCost {
1321            seek_count: 0,
1322            storage_cost: StorageCost::default(),
1323            storage_loaded_bytes: 0,
1324            hash_node_calls: 0,
1325            sinsemilla_hash_calls: 3,
1326        };
1327        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1328        let expected = fv.hashing.sinsemilla_base * 3;
1329        assert_eq!(result, expected);
1330    }
1331
1332    #[test]
1333    fn ephemeral_cost_all_components_combined() {
1334        let fv = fee_version();
1335        let cost = OperationCost {
1336            seek_count: 2,
1337            storage_cost: StorageCost {
1338                added_bytes: 10,
1339                replaced_bytes: 20,
1340                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(30),
1341            },
1342            storage_loaded_bytes: 40,
1343            hash_node_calls: 5,
1344            sinsemilla_hash_calls: 1,
1345        };
1346        let result = cost.ephemeral_cost(fv).expect("should not overflow");
1347
1348        let seek_cost = 2u64 * fv.storage.storage_seek_cost;
1349        let processing_per_byte = fv.storage.storage_processing_credit_per_byte;
1350        let added_cost = 10u64 * processing_per_byte;
1351        let replaced_cost = 20u64 * processing_per_byte;
1352        let removed_cost = 30u64 * processing_per_byte;
1353        let loaded_cost = 40u64 * fv.storage.storage_load_credit_per_byte;
1354        let blake3_total = fv.hashing.blake3_base + fv.hashing.blake3_per_block;
1355        let hash_cost = blake3_total * 5;
1356        let sinsemilla_cost = fv.hashing.sinsemilla_base * 1;
1357
1358        let expected = seek_cost
1359            + added_cost
1360            + replaced_cost
1361            + loaded_cost
1362            + removed_cost
1363            + hash_cost
1364            + sinsemilla_cost;
1365        assert_eq!(result, expected);
1366    }
1367
1368    #[test]
1369    fn ephemeral_cost_overflow_seek_cost() {
1370        let fv = &FeeVersion {
1371            storage: FeeStorageVersion {
1372                storage_seek_cost: u64::MAX,
1373                ..fee_version().storage.clone()
1374            },
1375            ..fee_version().clone()
1376        };
1377        let cost = OperationCost {
1378            seek_count: 2, // 2 * u64::MAX overflows
1379            storage_cost: StorageCost::default(),
1380            storage_loaded_bytes: 0,
1381            hash_node_calls: 0,
1382            sinsemilla_hash_calls: 0,
1383        };
1384        let result = cost.ephemeral_cost(fv);
1385        assert!(result.is_err(), "expected overflow error for seek cost");
1386    }
1387
1388    #[test]
1389    fn ephemeral_cost_overflow_storage_written_bytes() {
1390        let fv = &FeeVersion {
1391            storage: FeeStorageVersion {
1392                storage_processing_credit_per_byte: u64::MAX,
1393                ..fee_version().storage.clone()
1394            },
1395            ..fee_version().clone()
1396        };
1397        let cost = OperationCost {
1398            seek_count: 0,
1399            storage_cost: StorageCost {
1400                added_bytes: 2, // 2 * u64::MAX overflows
1401                replaced_bytes: 0,
1402                removed_bytes: StorageRemovedBytes::NoStorageRemoval,
1403            },
1404            storage_loaded_bytes: 0,
1405            hash_node_calls: 0,
1406            sinsemilla_hash_calls: 0,
1407        };
1408        let result = cost.ephemeral_cost(fv);
1409        assert!(
1410            result.is_err(),
1411            "expected overflow error for storage written bytes"
1412        );
1413    }
1414
1415    #[test]
1416    fn ephemeral_cost_overflow_loaded_bytes() {
1417        let fv = &FeeVersion {
1418            storage: FeeStorageVersion {
1419                storage_load_credit_per_byte: u64::MAX,
1420                ..fee_version().storage.clone()
1421            },
1422            ..fee_version().clone()
1423        };
1424        let cost = OperationCost {
1425            seek_count: 0,
1426            storage_cost: StorageCost::default(),
1427            storage_loaded_bytes: 2, // 2 * u64::MAX overflows
1428            hash_node_calls: 0,
1429            sinsemilla_hash_calls: 0,
1430        };
1431        let result = cost.ephemeral_cost(fv);
1432        assert!(
1433            result.is_err(),
1434            "expected overflow error for loaded bytes cost"
1435        );
1436    }
1437
1438    #[test]
1439    fn ephemeral_cost_overflow_in_addition_chain() {
1440        // Use values that individually do not overflow but whose sum does.
1441        let fv = fee_version();
1442        let cost = OperationCost {
1443            seek_count: u32::MAX,
1444            storage_cost: StorageCost {
1445                added_bytes: u32::MAX,
1446                replaced_bytes: u32::MAX,
1447                removed_bytes: StorageRemovedBytes::BasicStorageRemoval(u32::MAX),
1448            },
1449            storage_loaded_bytes: u64::MAX,
1450            hash_node_calls: u32::MAX,
1451            sinsemilla_hash_calls: u32::MAX,
1452        };
1453        let result = cost.ephemeral_cost(fv);
1454        assert!(
1455            result.is_err(),
1456            "expected overflow error when summing large components"
1457        );
1458    }
1459}