snarkvm_synthesizer_process/
cost.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{Process, Stack, StackProgramTypes};
17
18use crate::stack::StackRef;
19use console::{
20    prelude::*,
21    program::{FinalizeType, Identifier, LiteralType, PlaintextType},
22};
23use ledger_block::{Deployment, Execution, Transaction};
24use synthesizer_program::{CastType, Command, Finalize, Instruction, Operand, StackProgram};
25
26/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, synthesis cost, namespace cost)).
27pub fn deployment_cost<N: Network>(deployment: &Deployment<N>) -> Result<(u64, (u64, u64, u64))> {
28    // Determine the number of bytes in the deployment.
29    let size_in_bytes = deployment.size_in_bytes()?;
30    // Retrieve the program ID.
31    let program_id = deployment.program_id();
32    // Determine the number of characters in the program ID.
33    let num_characters = u32::try_from(program_id.name().to_string().len())?;
34    // Compute the number of combined variables in the program.
35    let num_combined_variables = deployment.num_combined_variables()?;
36    // Compute the number of combined constraints in the program.
37    let num_combined_constraints = deployment.num_combined_constraints()?;
38
39    // Compute the storage cost in microcredits.
40    let storage_cost = size_in_bytes
41        .checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
42        .ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
43
44    // Compute the synthesis cost in microcredits.
45    let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER;
46
47    // Compute the namespace cost in credits: 10^(10 - num_characters).
48    let namespace_cost = 10u64
49        .checked_pow(10u32.saturating_sub(num_characters))
50        .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
51        .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits.
52
53    // Compute the total cost in microcredits.
54    let total_cost = storage_cost
55        .checked_add(synthesis_cost)
56        .and_then(|x| x.checked_add(namespace_cost))
57        .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
58
59    Ok((total_cost, (storage_cost, synthesis_cost, namespace_cost)))
60}
61
62/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)).
63pub fn execution_cost_v2<N: Network>(process: &Process<N>, execution: &Execution<N>) -> Result<(u64, (u64, u64))> {
64    // Compute the storage cost in microcredits.
65    let storage_cost = execution_storage_cost::<N>(execution.size_in_bytes()?);
66
67    // Get the root transition.
68    let transition = execution.peek()?;
69
70    // Get the finalize cost for the root transition.
71    let stack = process.get_stack(transition.program_id())?;
72    let finalize_cost = cost_in_microcredits_v2(&stack, transition.function_name())?;
73
74    // Compute the total cost in microcredits.
75    let total_cost = storage_cost
76        .checked_add(finalize_cost)
77        .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
78
79    Ok((total_cost, (storage_cost, finalize_cost)))
80}
81
82/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)).
83pub fn execution_cost_v1<N: Network>(process: &Process<N>, execution: &Execution<N>) -> Result<(u64, (u64, u64))> {
84    // Compute the storage cost in microcredits.
85    let storage_cost = execution_storage_cost::<N>(execution.size_in_bytes()?);
86
87    // Get the root transition.
88    let transition = execution.peek()?;
89
90    // Get the finalize cost for the root transition.
91    let stack = process.get_stack(transition.program_id())?;
92    let finalize_cost = cost_in_microcredits_v1(&stack, transition.function_name())?;
93
94    // Compute the total cost in microcredits.
95    let total_cost = storage_cost
96        .checked_add(finalize_cost)
97        .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
98
99    Ok((total_cost, (storage_cost, finalize_cost)))
100}
101
102/// Returns the storage cost in microcredits for a program execution.
103fn execution_storage_cost<N: Network>(size_in_bytes: u64) -> u64 {
104    if size_in_bytes > N::EXECUTION_STORAGE_PENALTY_THRESHOLD {
105        size_in_bytes.saturating_mul(size_in_bytes).saturating_div(N::EXECUTION_STORAGE_FEE_SCALING_FACTOR)
106    } else {
107        size_in_bytes
108    }
109}
110
111// Finalize costs for compute heavy operations, derived as:
112// `BASE_COST + (PER_BYTE_COST * SIZE_IN_BYTES)`.
113
114const CAST_BASE_COST: u64 = 500;
115const CAST_PER_BYTE_COST: u64 = 30;
116
117const HASH_BASE_COST: u64 = 10_000;
118const HASH_PER_BYTE_COST: u64 = 30;
119
120const HASH_BHP_BASE_COST: u64 = 50_000;
121const HASH_BHP_PER_BYTE_COST: u64 = 300;
122
123const HASH_PSD_BASE_COST: u64 = 40_000;
124const HASH_PSD_PER_BYTE_COST: u64 = 75;
125
126#[derive(Copy, Clone)]
127pub enum ConsensusFeeVersion {
128    V1,
129    V2,
130}
131
132const MAPPING_BASE_COST_V1: u64 = 10_000;
133const MAPPING_BASE_COST_V2: u64 = 1_500;
134const MAPPING_PER_BYTE_COST: u64 = 10;
135
136const SET_BASE_COST: u64 = 10_000;
137const SET_PER_BYTE_COST: u64 = 100;
138
139/// A helper function to determine the plaintext type in bytes.
140fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, plaintext_type: &PlaintextType<N>) -> Result<u64> {
141    match plaintext_type {
142        PlaintextType::Literal(literal_type) => Ok(literal_type.size_in_bytes::<N>() as u64),
143        PlaintextType::Struct(struct_name) => {
144            // Retrieve the struct from the stack.
145            let struct_ = stack.program().get_struct(struct_name)?;
146            // Retrieve the size of the struct name.
147            let size_of_name = struct_.name().to_bytes_le()?.len() as u64;
148            // Retrieve the size of all the members of the struct.
149            let size_of_members = struct_.members().iter().try_fold(0u64, |acc, (_, member_type)| {
150                acc.checked_add(plaintext_size_in_bytes(stack, member_type)?).ok_or(anyhow!(
151                    "Overflowed while computing the size of the struct '{}/{struct_name}' - {member_type}",
152                    stack.program_id()
153                ))
154            })?;
155            // Return the size of the struct.
156            Ok(size_of_name.saturating_add(size_of_members))
157        }
158        PlaintextType::Array(array_type) => {
159            // Retrieve the number of elements in the array.
160            let num_elements = **array_type.length() as u64;
161            // Compute the size of an array element.
162            let size_of_element = plaintext_size_in_bytes(stack, array_type.next_element_type())?;
163            // Return the size of the array.
164            Ok(num_elements.saturating_mul(size_of_element))
165        }
166    }
167}
168
169/// A helper function to compute the following: base_cost + (byte_multiplier * size_of_operands).
170fn cost_in_size<'a, N: Network>(
171    stack: &Stack<N>,
172    finalize: &Finalize<N>,
173    operands: impl IntoIterator<Item = &'a Operand<N>>,
174    byte_multiplier: u64,
175    base_cost: u64,
176) -> Result<u64> {
177    // Retrieve the finalize types.
178    let finalize_types = stack.get_finalize_types(finalize.name())?;
179    // Compute the size of the operands.
180    let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| {
181        // Determine the size of the operand.
182        let operand_size = match finalize_types.get_type_from_operand(stack, operand)? {
183            FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?,
184            FinalizeType::Future(future) => {
185                bail!("Future '{future}' is not a valid operand in the finalize scope");
186            }
187        };
188        // Safely add the size to the accumulator.
189        acc.checked_add(operand_size).ok_or(anyhow!(
190            "Overflowed while computing the size of the operand '{operand}' in '{}/{}' (finalize)",
191            stack.program_id(),
192            finalize.name()
193        ))
194    })?;
195    // Return the cost.
196    Ok(base_cost.saturating_add(byte_multiplier.saturating_mul(size_of_operands)))
197}
198
199/// Returns the the cost of a command in a finalize scope.
200pub fn cost_per_command<N: Network>(
201    stack: &Stack<N>,
202    finalize: &Finalize<N>,
203    command: &Command<N>,
204    consensus_fee_version: ConsensusFeeVersion,
205) -> Result<u64> {
206    let mapping_base_cost = match consensus_fee_version {
207        ConsensusFeeVersion::V1 => MAPPING_BASE_COST_V1,
208        ConsensusFeeVersion::V2 => MAPPING_BASE_COST_V2,
209    };
210
211    match command {
212        Command::Instruction(Instruction::Abs(_)) => Ok(500),
213        Command::Instruction(Instruction::AbsWrapped(_)) => Ok(500),
214        Command::Instruction(Instruction::Add(_)) => Ok(500),
215        Command::Instruction(Instruction::AddWrapped(_)) => Ok(500),
216        Command::Instruction(Instruction::And(_)) => Ok(500),
217        Command::Instruction(Instruction::AssertEq(_)) => Ok(500),
218        Command::Instruction(Instruction::AssertNeq(_)) => Ok(500),
219        Command::Instruction(Instruction::Async(_)) => bail!("'async' is not supported in finalize"),
220        Command::Instruction(Instruction::Call(_)) => bail!("'call' is not supported in finalize"),
221        Command::Instruction(Instruction::Cast(cast)) => match cast.cast_type() {
222            CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
223            CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
224                .saturating_mul(CAST_PER_BYTE_COST)
225                .saturating_add(CAST_BASE_COST)),
226            CastType::GroupXCoordinate
227            | CastType::GroupYCoordinate
228            | CastType::Record(_)
229            | CastType::ExternalRecord(_) => Ok(500),
230        },
231        Command::Instruction(Instruction::CastLossy(cast_lossy)) => match cast_lossy.cast_type() {
232            CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
233            CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
234                .saturating_mul(CAST_PER_BYTE_COST)
235                .saturating_add(CAST_BASE_COST)),
236            CastType::GroupXCoordinate
237            | CastType::GroupYCoordinate
238            | CastType::Record(_)
239            | CastType::ExternalRecord(_) => Ok(500),
240        },
241        Command::Instruction(Instruction::CommitBHP256(commit)) => {
242            cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
243        }
244        Command::Instruction(Instruction::CommitBHP512(commit)) => {
245            cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
246        }
247        Command::Instruction(Instruction::CommitBHP768(commit)) => {
248            cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
249        }
250        Command::Instruction(Instruction::CommitBHP1024(commit)) => {
251            cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
252        }
253        Command::Instruction(Instruction::CommitPED64(commit)) => {
254            cost_in_size(stack, finalize, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
255        }
256        Command::Instruction(Instruction::CommitPED128(commit)) => {
257            cost_in_size(stack, finalize, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
258        }
259        Command::Instruction(Instruction::Div(div)) => {
260            // Ensure `div` has exactly two operands.
261            ensure!(div.operands().len() == 2, "'div' must contain exactly 2 operands");
262            // Retrieve the finalize types.
263            let finalize_types = stack.get_finalize_types(finalize.name())?;
264            // Retrieve the price by the operand type.
265            match finalize_types.get_type_from_operand(stack, &div.operands()[0])? {
266                FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
267                FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
268                FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'div' does not support arrays"),
269                FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'div' does not support structs"),
270                FinalizeType::Future(_) => bail!("'div' does not support futures"),
271            }
272        }
273        Command::Instruction(Instruction::DivWrapped(_)) => Ok(500),
274        Command::Instruction(Instruction::Double(_)) => Ok(500),
275        Command::Instruction(Instruction::GreaterThan(_)) => Ok(500),
276        Command::Instruction(Instruction::GreaterThanOrEqual(_)) => Ok(500),
277        Command::Instruction(Instruction::HashBHP256(hash)) => {
278            cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
279        }
280        Command::Instruction(Instruction::HashBHP512(hash)) => {
281            cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
282        }
283        Command::Instruction(Instruction::HashBHP768(hash)) => {
284            cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
285        }
286        Command::Instruction(Instruction::HashBHP1024(hash)) => {
287            cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
288        }
289        Command::Instruction(Instruction::HashKeccak256(hash)) => {
290            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
291        }
292        Command::Instruction(Instruction::HashKeccak384(hash)) => {
293            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
294        }
295        Command::Instruction(Instruction::HashKeccak512(hash)) => {
296            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
297        }
298        Command::Instruction(Instruction::HashPED64(hash)) => {
299            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
300        }
301        Command::Instruction(Instruction::HashPED128(hash)) => {
302            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
303        }
304        Command::Instruction(Instruction::HashPSD2(hash)) => {
305            cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
306        }
307        Command::Instruction(Instruction::HashPSD4(hash)) => {
308            cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
309        }
310        Command::Instruction(Instruction::HashPSD8(hash)) => {
311            cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
312        }
313        Command::Instruction(Instruction::HashSha3_256(hash)) => {
314            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
315        }
316        Command::Instruction(Instruction::HashSha3_384(hash)) => {
317            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
318        }
319        Command::Instruction(Instruction::HashSha3_512(hash)) => {
320            cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
321        }
322        Command::Instruction(Instruction::HashManyPSD2(_)) => {
323            bail!("`hash_many.psd2` is not supported in finalize")
324        }
325        Command::Instruction(Instruction::HashManyPSD4(_)) => {
326            bail!("`hash_many.psd4` is not supported in finalize")
327        }
328        Command::Instruction(Instruction::HashManyPSD8(_)) => {
329            bail!("`hash_many.psd8` is not supported in finalize")
330        }
331        Command::Instruction(Instruction::Inv(_)) => Ok(2_500),
332        Command::Instruction(Instruction::IsEq(_)) => Ok(500),
333        Command::Instruction(Instruction::IsNeq(_)) => Ok(500),
334        Command::Instruction(Instruction::LessThan(_)) => Ok(500),
335        Command::Instruction(Instruction::LessThanOrEqual(_)) => Ok(500),
336        Command::Instruction(Instruction::Modulo(_)) => Ok(500),
337        Command::Instruction(Instruction::Mul(mul)) => {
338            // Ensure `mul` has exactly two operands.
339            ensure!(mul.operands().len() == 2, "'mul' must contain exactly 2 operands");
340            // Retrieve the finalize types.
341            let finalize_types = stack.get_finalize_types(finalize.name())?;
342            // Retrieve the price by operand type.
343            match finalize_types.get_type_from_operand(stack, &mul.operands()[0])? {
344                FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Group)) => Ok(10_000),
345                FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Scalar)) => Ok(10_000),
346                FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
347                FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'mul' does not support arrays"),
348                FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'mul' does not support structs"),
349                FinalizeType::Future(_) => bail!("'mul' does not support futures"),
350            }
351        }
352        Command::Instruction(Instruction::MulWrapped(_)) => Ok(500),
353        Command::Instruction(Instruction::Nand(_)) => Ok(500),
354        Command::Instruction(Instruction::Neg(_)) => Ok(500),
355        Command::Instruction(Instruction::Nor(_)) => Ok(500),
356        Command::Instruction(Instruction::Not(_)) => Ok(500),
357        Command::Instruction(Instruction::Or(_)) => Ok(500),
358        Command::Instruction(Instruction::Pow(pow)) => {
359            // Ensure `pow` has at least one operand.
360            ensure!(!pow.operands().is_empty(), "'pow' must contain at least 1 operand");
361            // Retrieve the finalize types.
362            let finalize_types = stack.get_finalize_types(finalize.name())?;
363            // Retrieve the price by operand type.
364            match finalize_types.get_type_from_operand(stack, &pow.operands()[0])? {
365                FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
366                FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
367                FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'pow' does not support arrays"),
368                FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'pow' does not support structs"),
369                FinalizeType::Future(_) => bail!("'pow' does not support futures"),
370            }
371        }
372        Command::Instruction(Instruction::PowWrapped(_)) => Ok(500),
373        Command::Instruction(Instruction::Rem(_)) => Ok(500),
374        Command::Instruction(Instruction::RemWrapped(_)) => Ok(500),
375        Command::Instruction(Instruction::SignVerify(sign)) => {
376            cost_in_size(stack, finalize, sign.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
377        }
378        Command::Instruction(Instruction::Shl(_)) => Ok(500),
379        Command::Instruction(Instruction::ShlWrapped(_)) => Ok(500),
380        Command::Instruction(Instruction::Shr(_)) => Ok(500),
381        Command::Instruction(Instruction::ShrWrapped(_)) => Ok(500),
382        Command::Instruction(Instruction::Square(_)) => Ok(500),
383        Command::Instruction(Instruction::SquareRoot(_)) => Ok(2_500),
384        Command::Instruction(Instruction::Sub(_)) => Ok(500),
385        Command::Instruction(Instruction::SubWrapped(_)) => Ok(500),
386        Command::Instruction(Instruction::Ternary(_)) => Ok(500),
387        Command::Instruction(Instruction::Xor(_)) => Ok(500),
388        Command::Await(_) => Ok(500),
389        Command::Contains(command) => {
390            cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
391        }
392        Command::Get(command) => {
393            cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
394        }
395        Command::GetOrUse(command) => {
396            cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
397        }
398        Command::RandChaCha(_) => Ok(25_000),
399        Command::Remove(_) => Ok(SET_BASE_COST),
400        Command::Set(command) => {
401            cost_in_size(stack, finalize, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST)
402        }
403        Command::BranchEq(_) | Command::BranchNeq(_) => Ok(500),
404        Command::Position(_) => Ok(100),
405    }
406}
407
408/// Returns the minimum number of microcredits required to run the finalize.
409pub fn cost_in_microcredits_v2<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
410    cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V2)
411}
412
413/// Returns the minimum number of microcredits required to run the finalize (deprecated).
414pub fn cost_in_microcredits_v1<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
415    cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V1)
416}
417
418// A helper function to compute the cost in microcredits for a given function.
419fn cost_in_microcredits<N: Network>(
420    stack: &Stack<N>,
421    function_name: &Identifier<N>,
422    consensus_fee_version: ConsensusFeeVersion,
423) -> Result<u64> {
424    // Initialize the base cost.
425    let mut finalize_cost = 0u64;
426    // Initialize a queue of finalize blocks to tally.
427    let mut finalizes = vec![(StackRef::Internal(stack), *function_name)];
428    // Initialize a counter for the number of finalize blocks seen.
429    let mut num_finalizes = 1;
430    // Iterate over the finalize blocks.
431    while let Some((stack_ref, function_name)) = finalizes.pop() {
432        // Ensure that the number of finalize blocks does not exceed the maximum.
433        // Note that one transition is reserved for the fee.
434        ensure!(
435            num_finalizes < Transaction::<N>::MAX_TRANSITIONS,
436            "The number of finalize blocks must be less than '{}'",
437            Transaction::<N>::MAX_TRANSITIONS
438        );
439        // Get the finalize logic. If the function does not have a finalize scope then no cost is incurred.
440        if let Some(finalize) = stack_ref.get_function_ref(&function_name)?.finalize_logic() {
441            // Queue the futures to be tallied.
442            for input in finalize.inputs() {
443                if let FinalizeType::Future(future) = input.finalize_type() {
444                    // Get the external stack for the future.
445                    let external_stack = stack_ref.get_external_stack(future.program_id())?;
446                    // Increment the number of finalize blocks seen.
447                    num_finalizes += 1;
448                    // Queue the future.
449                    finalizes.push((StackRef::External(external_stack), *future.resource()));
450                }
451            }
452            // Iterate over the commands in the finalize block.
453            for command in finalize.commands() {
454                // Sum the cost of all commands in the current future into the total running cost.
455                finalize_cost = finalize_cost
456                    .checked_add(cost_per_command(&stack_ref, finalize, command, consensus_fee_version)?)
457                    .ok_or(anyhow!("Finalize cost overflowed"))?;
458            }
459        }
460    }
461    Ok(finalize_cost)
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467    use crate::test_helpers::get_execution;
468
469    use console::network::{CanaryV0, MainnetV0, TestnetV0};
470    use synthesizer_program::Program;
471
472    // Test program with two functions just below and above the size threshold.
473    const SIZE_BOUNDARY_PROGRAM: &str = r#"
474program size_boundary.aleo;
475
476function under_five_thousand:
477    input r0 as group.public;
478    cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
479    cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
480    cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
481    output r2 as [[group; 9u32]; 10u32].public;
482    output r3 as [group; 7u32].public;
483
484function over_five_thousand:
485    input r0 as group.public;
486    cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
487    cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
488    cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
489    output r2 as [[group; 9u32]; 10u32].public;
490    output r3 as [group; 7u32].public;
491    output 5u64 as u64.public;
492    "#;
493    // Cost for a program +1 byte above the threshold.
494    const STORAGE_COST_ABOVE_THRESHOLD: u64 = 5002;
495    // Storage cost for an execution transaction at the maximum transaction size.
496    const STORAGE_COST_MAX: u64 = 3_276_800;
497
498    fn test_storage_cost_bounds<N: Network>() {
499        // Calculate the bounds directly above and below the size threshold.
500        let threshold = N::EXECUTION_STORAGE_PENALTY_THRESHOLD;
501        let threshold_lower_offset = threshold.saturating_sub(1);
502        let threshold_upper_offset = threshold.saturating_add(1);
503
504        // Test the storage cost bounds.
505        assert_eq!(execution_storage_cost::<N>(0), 0);
506        assert_eq!(execution_storage_cost::<N>(1), 1);
507        assert_eq!(execution_storage_cost::<N>(threshold_lower_offset), threshold_lower_offset);
508        assert_eq!(execution_storage_cost::<N>(threshold), threshold);
509        assert_eq!(execution_storage_cost::<N>(threshold_upper_offset), STORAGE_COST_ABOVE_THRESHOLD);
510        assert_eq!(execution_storage_cost::<N>(N::MAX_TRANSACTION_SIZE as u64), STORAGE_COST_MAX);
511    }
512
513    #[test]
514    fn test_storage_cost_bounds_for_all_networks() {
515        test_storage_cost_bounds::<CanaryV0>();
516        test_storage_cost_bounds::<MainnetV0>();
517        test_storage_cost_bounds::<TestnetV0>();
518    }
519
520    #[test]
521    fn test_storage_costs_compute_correctly() {
522        // Test the storage cost of an execution.
523        let threshold = MainnetV0::EXECUTION_STORAGE_PENALTY_THRESHOLD;
524
525        // Test the cost of an execution.
526        let mut process = Process::load().unwrap();
527
528        // Get the program.
529        let program = Program::from_str(SIZE_BOUNDARY_PROGRAM).unwrap();
530
531        // Get the program identifiers.
532        let under_5000 = Identifier::from_str("under_five_thousand").unwrap();
533        let over_5000 = Identifier::from_str("over_five_thousand").unwrap();
534
535        // Get execution and cost data.
536        let execution_under_5000 = get_execution(&mut process, &program, &under_5000, ["2group"].into_iter());
537        let execution_size_under_5000 = execution_under_5000.size_in_bytes().unwrap();
538        let (_, (storage_cost_under_5000, _)) = execution_cost_v2(&process, &execution_under_5000).unwrap();
539        let execution_over_5000 = get_execution(&mut process, &program, &over_5000, ["2group"].into_iter());
540        let execution_size_over_5000 = execution_over_5000.size_in_bytes().unwrap();
541        let (_, (storage_cost_over_5000, _)) = execution_cost_v2(&process, &execution_over_5000).unwrap();
542
543        // Ensure the sizes are below and above the threshold respectively.
544        assert!(execution_size_under_5000 < threshold);
545        assert!(execution_size_over_5000 > threshold);
546
547        // Ensure storage costs compute correctly.
548        assert_eq!(storage_cost_under_5000, execution_storage_cost::<MainnetV0>(execution_size_under_5000));
549        assert_eq!(storage_cost_over_5000, execution_storage_cost::<MainnetV0>(execution_size_over_5000));
550    }
551}