1use std::collections::HashMap;
17
18use crate::{Authorization, FinalizeTypes, Process, Stack, StackRef, StackTrait};
19
20use console::{
21 prelude::*,
22 program::{FinalizeType, Identifier, LiteralType, PlaintextType},
23};
24use snarkvm_algorithms::snark::varuna::VarunaVersion;
25use snarkvm_ledger_block::{Deployment, Execution, Transaction};
26use snarkvm_synthesizer_program::{CallDynamic, CastType, Command, GetRecordDynamic, Instruction, Operand};
27use snarkvm_synthesizer_snark::proof_size;
28
29pub type MinimumCost = u64;
30pub type StorageCost = u64;
31pub type SynthesisCost = u64;
32pub type ConstructorCost = u64;
33pub type NamespaceCost = u64;
34pub type FinalizeCost = u64;
35pub type DeployCostDetails = (StorageCost, SynthesisCost, ConstructorCost, NamespaceCost);
36pub type ExecuteCostDetails = (StorageCost, FinalizeCost);
37
38pub fn deployment_cost<N: Network>(
40 process: &Process<N>,
41 deployment: &Deployment<N>,
42 consensus_version: ConsensusVersion,
43) -> Result<(MinimumCost, DeployCostDetails)> {
44 if consensus_version >= ConsensusVersion::V10 {
45 deployment_cost_v2(process, deployment)
46 } else {
47 deployment_cost_v1(process, deployment)
48 }
49}
50
51pub fn execution_cost<N: Network>(
53 process: &Process<N>,
54 execution: &Execution<N>,
55 consensus_version: ConsensusVersion,
56) -> Result<(MinimumCost, ExecuteCostDetails)> {
57 let execution_size = execution.size_in_bytes()?;
58
59 execution_cost_given_size(process, execution, execution_size, consensus_version)
60}
61
62fn execution_cost_given_size<N: Network>(
64 process: &Process<N>,
65 execution: &Execution<N>,
66 execution_size: u64,
67 consensus_version: ConsensusVersion,
68) -> Result<(MinimumCost, ExecuteCostDetails)> {
69 if consensus_version >= ConsensusVersion::V10 {
70 execution_cost_v3(process, execution, execution_size)
71 } else if consensus_version >= ConsensusVersion::V2 {
72 execution_cost_v2(process, execution, execution_size)
73 } else {
74 execution_cost_v1(process, execution, execution_size)
75 }
76}
77
78pub fn execution_cost_for_authorization<N: Network>(
80 process: &Process<N>,
81 authorization: &Authorization<N>,
82 consensus_version: ConsensusVersion,
83) -> Result<(MinimumCost, ExecuteCostDetails)> {
84 ensure!(
85 consensus_version >= ConsensusVersion::V4,
86 "Execution-cost computation for authorization relies on proof-size estimation, which is only implemented for Varuna version >= V2 (consensus version >= V4)"
87 );
88
89 let reconstructed_execution =
92 Execution::from(authorization.transitions().values().cloned(), N::StateRoot::default(), None)?;
93
94 let mut circuit_frequencies = HashMap::new();
104
105 for transition in authorization.transitions().values() {
108 let entry =
109 circuit_frequencies.entry((*transition.program_id(), *transition.function_name())).or_insert(0usize);
110 *entry += 1;
111 }
112
113 let mut batch_sizes: Vec<usize> = circuit_frequencies.values().cloned().collect();
114
115 let n_input_records = Authorization::number_of_input_records(authorization.transitions().values());
118 if n_input_records > 0 {
119 batch_sizes.push(n_input_records);
120 }
121
122 let translations_for_transaction =
124 Authorization::translation_batch_sizes(process, authorization.transitions().values())?;
125 batch_sizes.extend(translations_for_transaction);
126
127 let hiding_mode = true;
129
130 let varuna_version = VarunaVersion::V2;
134
135 let expected_proof_size = u64::try_from(proof_size::<N>(&batch_sizes, varuna_version, hiding_mode)?)?;
136 let unproved_execution_size = reconstructed_execution.size_in_bytes()?;
137 let execution_size = unproved_execution_size.checked_add(expected_proof_size).ok_or(anyhow!(
138 "The execution size computation overflowed for an authorization when the proof was taken into account"
139 ))?;
140
141 execution_cost_given_size(process, &reconstructed_execution, execution_size, consensus_version)
142}
143
144pub fn deploy_compute_cost_in_microcredits(
148 cost_details: DeployCostDetails,
149 consensus_version: ConsensusVersion,
150) -> Result<u64> {
151 let (storage_cost, synthesis_cost, constructor_cost, _) = cost_details;
152 let cost_to_check = if consensus_version >= ConsensusVersion::V10 {
153 constructor_cost
159 } else {
160 storage_cost
162 .checked_add(synthesis_cost)
163 .and_then(|synthesis_cost| synthesis_cost.checked_add(constructor_cost))
164 .ok_or(anyhow!("The storage, synthesis, and constructor cost computation overflowed for a deployment"))?
165 };
166 Ok(cost_to_check)
167}
168
169pub fn execute_compute_cost_in_microcredits(
173 cost_details: ExecuteCostDetails,
174 consensus_version: ConsensusVersion,
175) -> Result<u64> {
176 let (storage_cost, finalize_cost) = cost_details;
177 let cost_to_check = if consensus_version >= ConsensusVersion::V10 {
178 finalize_cost
180 } else {
181 storage_cost
183 .checked_add(finalize_cost)
184 .ok_or(anyhow!("The storage and finalize cost computation overflowed for an execution"))?
185 };
186 Ok(cost_to_check)
187}
188
189pub fn deployment_cost_v2<N: Network>(
191 process: &Process<N>,
192 deployment: &Deployment<N>,
193) -> Result<(MinimumCost, DeployCostDetails)> {
194 let size_in_bytes = deployment.size_in_bytes()?;
196 let program_id = deployment.program_id();
198 let num_characters = u32::try_from(program_id.name().to_string().len())?;
200 let num_combined_variables = deployment.num_combined_variables()?;
202 let num_combined_constraints = deployment.num_combined_constraints()?;
204
205 let storage_cost = size_in_bytes
207 .checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
208 .ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
209
210 let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER
212 / N::ARC_0005_COMPUTE_DISCOUNT;
213
214 let stack = Stack::new(process, deployment.program())?;
216
217 let constructor_cost = constructor_cost_in_microcredits_v2(&stack)?;
219
220 for function in deployment.program().functions().values() {
222 let finalize_cost = minimum_cost_in_microcredits_v3(&stack, function.name())?;
224 ensure!(
226 finalize_cost <= N::TRANSACTION_SPEND_LIMIT[1].1,
227 "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'",
228 function.name(),
229 N::TRANSACTION_SPEND_LIMIT[1].1
230 );
231 }
232
233 let namespace_cost = 10u64
235 .checked_pow(10u32.saturating_sub(num_characters))
236 .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
237 .saturating_mul(1_000_000); let minimum_cost = storage_cost
241 .checked_add(synthesis_cost)
242 .and_then(|x| x.checked_add(constructor_cost))
243 .and_then(|x| x.checked_add(namespace_cost))
244 .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
245
246 Ok((minimum_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)))
247}
248
249pub fn deployment_cost_v1<N: Network>(
251 process: &Process<N>,
252 deployment: &Deployment<N>,
253) -> Result<(MinimumCost, DeployCostDetails)> {
254 let size_in_bytes = deployment.size_in_bytes()?;
256 let program_id = deployment.program_id();
258 let num_characters = u32::try_from(program_id.name().to_string().len())?;
260 let num_combined_variables = deployment.num_combined_variables()?;
262 let num_combined_constraints = deployment.num_combined_constraints()?;
264
265 let storage_cost = size_in_bytes
267 .checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
268 .ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
269
270 let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER;
272
273 let stack = Stack::new(process, deployment.program())?;
275
276 let constructor_cost = constructor_cost_in_microcredits_v1(&stack)?;
278
279 for function in deployment.program().functions().values() {
281 let finalize_cost = minimum_cost_in_microcredits_v2(&stack, function.name())?;
283 ensure!(
285 finalize_cost <= N::TRANSACTION_SPEND_LIMIT[0].1,
286 "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'",
287 function.name(),
288 N::TRANSACTION_SPEND_LIMIT[0].1
289 );
290 }
291
292 let namespace_cost = 10u64
294 .checked_pow(10u32.saturating_sub(num_characters))
295 .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
296 .saturating_mul(1_000_000); let minimum_cost = storage_cost
300 .checked_add(synthesis_cost)
301 .and_then(|x| x.checked_add(constructor_cost))
302 .and_then(|x| x.checked_add(namespace_cost))
303 .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
304
305 Ok((minimum_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)))
306}
307
308fn execution_cost_v3<N: Network>(
311 process: &Process<N>,
312 execution: &Execution<N>,
313 execution_size: u64,
314) -> Result<(MinimumCost, ExecuteCostDetails)> {
315 let storage_cost = execution_storage_cost::<N>(execution_size);
317
318 let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V3)?;
321
322 let total_cost = storage_cost
324 .checked_add(finalize_cost)
325 .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
326
327 Ok((total_cost, (storage_cost, finalize_cost)))
328}
329
330fn execution_cost_v2<N: Network>(
333 process: &Process<N>,
334 execution: &Execution<N>,
335 execution_size: u64,
336) -> Result<(MinimumCost, ExecuteCostDetails)> {
337 let storage_cost = execution_storage_cost::<N>(execution_size);
339
340 let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V2)?;
343
344 let total_cost = storage_cost
346 .checked_add(finalize_cost)
347 .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
348
349 Ok((total_cost, (storage_cost, finalize_cost)))
350}
351
352fn execution_cost_v1<N: Network>(
355 process: &Process<N>,
356 execution: &Execution<N>,
357 execution_size: u64,
358) -> Result<(MinimumCost, ExecuteCostDetails)> {
359 let storage_cost = execution_storage_cost::<N>(execution_size);
361
362 let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V1)?;
365
366 let total_cost = storage_cost
368 .checked_add(finalize_cost)
369 .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
370
371 Ok((total_cost, (storage_cost, finalize_cost)))
372}
373
374fn execution_storage_cost<N: Network>(size_in_bytes: u64) -> u64 {
376 if size_in_bytes > N::EXECUTION_STORAGE_PENALTY_THRESHOLD {
377 size_in_bytes.saturating_mul(size_in_bytes).saturating_div(N::EXECUTION_STORAGE_FEE_SCALING_FACTOR)
378 } else {
379 size_in_bytes
380 }
381}
382
383const CAST_BASE_COST: u64 = 500;
387const CAST_PER_BYTE_COST: u64 = 30;
388
389const HASH_BASE_COST: u64 = 10_000;
390const HASH_PER_BYTE_COST: u64 = 30;
391
392const HASH_BHP_BASE_COST: u64 = 50_000;
393const HASH_BHP_PER_BYTE_COST: u64 = 300;
394
395const HASH_PSD_BASE_COST: u64 = 40_000;
396const HASH_PSD_PER_BYTE_COST: u64 = 75;
397
398const ECDSA_VERIFY_BASE_COST: u64 = 60_000;
399const ECDSA_VERIFY_ETH_BASE_COST: u64 = 75_000;
400
401const SNARK_VERIFY_BASE_COST: u64 = 100_000;
402const SNARK_VERIFY_PER_BYTE_COST: u64 = 50;
403
404#[derive(Copy, Clone)]
405pub enum ConsensusFeeVersion {
406 V1,
407 V2,
408 V3,
409}
410
411const MAPPING_BASE_COST_V1: u64 = 10_000;
412const MAPPING_BASE_COST_V2: u64 = 1_500;
413const MAPPING_PER_BYTE_COST: u64 = 10;
414
415const SET_BASE_COST: u64 = 10_000;
416const SET_PER_BYTE_COST: u64 = 100;
417
418fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, plaintext_type: &PlaintextType<N>) -> Result<u64> {
420 match plaintext_type {
421 PlaintextType::Literal(literal_type) => Ok(literal_type.size_in_bytes::<N>() as u64),
422 PlaintextType::Struct(struct_name) => {
423 let struct_ = stack.program().get_struct(struct_name)?;
425 let size_of_name = struct_.name().to_bytes_le()?.len() as u64;
427 let size_of_members = struct_.members().iter().try_fold(0u64, |acc, (_, member_type)| {
429 acc.checked_add(plaintext_size_in_bytes(stack, member_type)?).ok_or(anyhow!(
430 "Overflowed while computing the size of the struct '{}/{struct_name}' - {member_type}",
431 stack.program_id()
432 ))
433 })?;
434 Ok(size_of_name.saturating_add(size_of_members))
436 }
437 PlaintextType::ExternalStruct(locator) => {
438 let external_stack = stack.get_external_stack(locator.program_id())?;
439 plaintext_size_in_bytes(&*external_stack, &PlaintextType::Struct(*locator.resource()))
440 }
441 PlaintextType::Array(array_type) => {
442 let num_elements = **array_type.length() as u64;
444 let size_of_element = plaintext_size_in_bytes(stack, array_type.next_element_type())?;
446 Ok(num_elements.saturating_mul(size_of_element))
448 }
449 }
450}
451
452fn cost_in_size<'a, N: Network>(
454 stack: &Stack<N>,
455 finalize_types: &FinalizeTypes<N>,
456 operands: impl IntoIterator<Item = &'a Operand<N>>,
457 byte_multiplier: u64,
458 base_cost: u64,
459) -> Result<u64> {
460 let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| {
462 let operand_size = match finalize_types.get_type_from_operand(stack, operand)? {
464 FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?,
465 FinalizeType::Future(future) => {
466 bail!("Future '{future}' is not a valid operand");
467 }
468 FinalizeType::DynamicFuture => {
469 bail!("Dynamic future is not a valid operand");
470 }
471 };
472 acc.checked_add(operand_size).ok_or(anyhow!(
474 "Overflowed while computing the size of the operand '{operand}' in '{}'",
475 stack.program_id(),
476 ))
477 })?;
478 Ok(base_cost.saturating_add(byte_multiplier.saturating_mul(size_of_operands)))
480}
481
482pub fn cost_per_command<N: Network>(
484 stack: &Stack<N>,
485 finalize_types: &FinalizeTypes<N>,
486 command: &Command<N>,
487 consensus_fee_version: ConsensusFeeVersion,
488) -> Result<u64> {
489 let mapping_base_cost = match consensus_fee_version {
490 ConsensusFeeVersion::V1 => MAPPING_BASE_COST_V1,
491 ConsensusFeeVersion::V2 | ConsensusFeeVersion::V3 => MAPPING_BASE_COST_V2,
492 };
493
494 match command {
495 Command::Instruction(Instruction::Abs(_)) => Ok(500),
496 Command::Instruction(Instruction::AbsWrapped(_)) => Ok(500),
497 Command::Instruction(Instruction::Add(_)) => Ok(500),
498 Command::Instruction(Instruction::AddWrapped(_)) => Ok(500),
499 Command::Instruction(Instruction::And(_)) => Ok(500),
500 Command::Instruction(Instruction::AssertEq(_)) => Ok(500),
501 Command::Instruction(Instruction::AssertNeq(_)) => Ok(500),
502 Command::Instruction(Instruction::Async(_)) => bail!("'async' is not supported in finalize"),
503 Command::Instruction(Instruction::Call(_)) => bail!("'call' is not supported in finalize"),
504 Command::Instruction(Instruction::CallDynamic(_)) => {
505 bail!("'{}' is not supported in finalize", CallDynamic::<N>::opcode())
506 }
507 Command::Instruction(Instruction::Cast(cast)) => match cast.cast_type() {
508 CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
509 CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
510 .saturating_mul(CAST_PER_BYTE_COST)
511 .saturating_add(CAST_BASE_COST)),
512 CastType::GroupXCoordinate | CastType::GroupYCoordinate => Ok(500),
513 CastType::Record(_) => bail!("'cast' to a record is not supported in finalize"),
514 CastType::ExternalRecord(_) => bail!("'cast' to an external record is not supported in finalize"),
515 CastType::DynamicRecord => bail!("'cast' to a dynamic record is not supported in finalize"),
516 },
517 Command::Instruction(Instruction::CastLossy(cast_lossy)) => match cast_lossy.cast_type() {
518 CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
519 CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
520 .saturating_mul(CAST_PER_BYTE_COST)
521 .saturating_add(CAST_BASE_COST)),
522 CastType::GroupXCoordinate | CastType::GroupYCoordinate => Ok(500),
523 CastType::Record(_) => bail!("'cast.lossy' to a record is not supported in finalize"),
524 CastType::ExternalRecord(_) => bail!("'cast.lossy' to an external record is not supported in finalize"),
525 CastType::DynamicRecord => bail!("'cast.lossy' to a dynamic record is not supported in finalize"),
526 },
527 Command::Instruction(Instruction::CommitBHP256(commit)) => {
528 cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
529 }
530 Command::Instruction(Instruction::CommitBHP512(commit)) => {
531 cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
532 }
533 Command::Instruction(Instruction::CommitBHP768(commit)) => {
534 cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
535 }
536 Command::Instruction(Instruction::CommitBHP1024(commit)) => {
537 cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
538 }
539 Command::Instruction(Instruction::CommitPED64(commit)) => {
540 cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
541 }
542 Command::Instruction(Instruction::CommitPED128(commit)) => {
543 cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
544 }
545 Command::Instruction(Instruction::DeserializeBits(deserialize)) => {
546 Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(deserialize.operand_type().clone()))?
547 .saturating_mul(CAST_PER_BYTE_COST)
548 .saturating_add(CAST_BASE_COST))
549 }
550 Command::Instruction(Instruction::DeserializeBitsRaw(deserialize)) => {
551 Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(deserialize.operand_type().clone()))?
552 .saturating_mul(CAST_PER_BYTE_COST)
553 .saturating_add(CAST_BASE_COST))
554 }
555 Command::Instruction(Instruction::Div(div)) => {
556 ensure!(div.operands().len() == 2, "'div' must contain exactly 2 operands");
558 match finalize_types.get_type_from_operand(stack, &div.operands()[0])? {
560 FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
561 FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
562 FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'div' does not support arrays"),
563 FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
564 bail!("'div' does not support structs")
565 }
566 FinalizeType::Future(_) => bail!("'div' does not support futures"),
567 FinalizeType::DynamicFuture => bail!("'div' does not support dynamic futures"),
568 }
569 }
570 Command::Instruction(Instruction::DivWrapped(_)) => Ok(500),
571 Command::Instruction(Instruction::Double(_)) => Ok(500),
572 Command::Instruction(Instruction::ECDSAVerifyDigest(_)) => Ok(ECDSA_VERIFY_BASE_COST),
573 Command::Instruction(Instruction::ECDSAVerifyDigestEth(_)) => Ok(ECDSA_VERIFY_ETH_BASE_COST),
574 Command::Instruction(Instruction::ECDSAVerifyKeccak256(ecdsa)) => {
575 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
576 }
577 Command::Instruction(Instruction::ECDSAVerifyKeccak256Raw(ecdsa)) => {
578 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
579 }
580 Command::Instruction(Instruction::ECDSAVerifyKeccak256Eth(ecdsa)) => {
581 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
582 }
583 Command::Instruction(Instruction::ECDSAVerifyKeccak384(ecdsa)) => {
584 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
585 }
586 Command::Instruction(Instruction::ECDSAVerifyKeccak384Raw(ecdsa)) => {
587 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
588 }
589 Command::Instruction(Instruction::ECDSAVerifyKeccak384Eth(ecdsa)) => {
590 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
591 }
592 Command::Instruction(Instruction::ECDSAVerifyKeccak512(ecdsa)) => {
593 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
594 }
595 Command::Instruction(Instruction::ECDSAVerifyKeccak512Raw(ecdsa)) => {
596 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
597 }
598 Command::Instruction(Instruction::ECDSAVerifyKeccak512Eth(ecdsa)) => {
599 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
600 }
601 Command::Instruction(Instruction::ECDSAVerifySha3_256(ecdsa)) => {
602 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
603 }
604 Command::Instruction(Instruction::ECDSAVerifySha3_256Raw(ecdsa)) => {
605 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
606 }
607 Command::Instruction(Instruction::ECDSAVerifySha3_256Eth(ecdsa)) => {
608 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
609 }
610 Command::Instruction(Instruction::ECDSAVerifySha3_384(ecdsa)) => {
611 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
612 }
613 Command::Instruction(Instruction::ECDSAVerifySha3_384Raw(ecdsa)) => {
614 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
615 }
616 Command::Instruction(Instruction::ECDSAVerifySha3_384Eth(ecdsa)) => {
617 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
618 }
619 Command::Instruction(Instruction::ECDSAVerifySha3_512(ecdsa)) => {
620 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
621 }
622 Command::Instruction(Instruction::ECDSAVerifySha3_512Raw(ecdsa)) => {
623 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
624 }
625 Command::Instruction(Instruction::ECDSAVerifySha3_512Eth(ecdsa)) => {
626 cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
627 }
628 Command::Instruction(Instruction::GetRecordDynamic(_)) => {
629 bail!("'{}' is not supported in finalize", GetRecordDynamic::<N>::opcode())
630 }
631 Command::Instruction(Instruction::GreaterThan(_)) => Ok(500),
632 Command::Instruction(Instruction::GreaterThanOrEqual(_)) => Ok(500),
633 Command::Instruction(Instruction::HashBHP256(hash)) => {
634 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
635 }
636 Command::Instruction(Instruction::HashBHP256Raw(hash)) => {
637 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
638 }
639 Command::Instruction(Instruction::HashBHP512(hash)) => {
640 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
641 }
642 Command::Instruction(Instruction::HashBHP512Raw(hash)) => {
643 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
644 }
645 Command::Instruction(Instruction::HashBHP768(hash)) => {
646 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
647 }
648 Command::Instruction(Instruction::HashBHP768Raw(hash)) => {
649 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
650 }
651 Command::Instruction(Instruction::HashBHP1024(hash)) => {
652 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
653 }
654 Command::Instruction(Instruction::HashBHP1024Raw(hash)) => {
655 cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
656 }
657 Command::Instruction(Instruction::HashKeccak256(hash)) => {
658 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
659 }
660 Command::Instruction(Instruction::HashKeccak256Raw(hash)) => {
661 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
662 }
663 Command::Instruction(Instruction::HashKeccak256Native(hash)) => {
664 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
665 }
666 Command::Instruction(Instruction::HashKeccak256NativeRaw(hash)) => {
667 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
668 }
669 Command::Instruction(Instruction::HashKeccak384(hash)) => {
670 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
671 }
672 Command::Instruction(Instruction::HashKeccak384Raw(hash)) => {
673 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
674 }
675 Command::Instruction(Instruction::HashKeccak384Native(hash)) => {
676 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
677 }
678 Command::Instruction(Instruction::HashKeccak384NativeRaw(hash)) => {
679 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
680 }
681 Command::Instruction(Instruction::HashKeccak512(hash)) => {
682 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
683 }
684 Command::Instruction(Instruction::HashKeccak512Raw(hash)) => {
685 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
686 }
687 Command::Instruction(Instruction::HashKeccak512Native(hash)) => {
688 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
689 }
690 Command::Instruction(Instruction::HashKeccak512NativeRaw(hash)) => {
691 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
692 }
693 Command::Instruction(Instruction::HashPED64(hash)) => {
694 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
695 }
696 Command::Instruction(Instruction::HashPED64Raw(hash)) => {
697 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
698 }
699 Command::Instruction(Instruction::HashPED128(hash)) => {
700 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
701 }
702 Command::Instruction(Instruction::HashPED128Raw(hash)) => {
703 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
704 }
705 Command::Instruction(Instruction::HashPSD2(hash)) => {
706 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
707 }
708 Command::Instruction(Instruction::HashPSD2Raw(hash)) => {
709 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
710 }
711 Command::Instruction(Instruction::HashPSD4(hash)) => {
712 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
713 }
714 Command::Instruction(Instruction::HashPSD4Raw(hash)) => {
715 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
716 }
717 Command::Instruction(Instruction::HashPSD8(hash)) => {
718 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
719 }
720 Command::Instruction(Instruction::HashPSD8Raw(hash)) => {
721 cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
722 }
723 Command::Instruction(Instruction::HashSha3_256(hash)) => {
724 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
725 }
726 Command::Instruction(Instruction::HashSha3_256Raw(hash)) => {
727 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
728 }
729 Command::Instruction(Instruction::HashSha3_256Native(hash)) => {
730 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
731 }
732 Command::Instruction(Instruction::HashSha3_256NativeRaw(hash)) => {
733 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
734 }
735 Command::Instruction(Instruction::HashSha3_384(hash)) => {
736 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
737 }
738 Command::Instruction(Instruction::HashSha3_384Raw(hash)) => {
739 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
740 }
741 Command::Instruction(Instruction::HashSha3_384Native(hash)) => {
742 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
743 }
744 Command::Instruction(Instruction::HashSha3_384NativeRaw(hash)) => {
745 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
746 }
747 Command::Instruction(Instruction::HashSha3_512(hash)) => {
748 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
749 }
750 Command::Instruction(Instruction::HashSha3_512Raw(hash)) => {
751 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
752 }
753 Command::Instruction(Instruction::HashSha3_512Native(hash)) => {
754 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
755 }
756 Command::Instruction(Instruction::HashSha3_512NativeRaw(hash)) => {
757 cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
758 }
759 Command::Instruction(Instruction::HashManyPSD2(_)) => {
760 bail!("`hash_many.psd2` is not supported in finalize")
761 }
762 Command::Instruction(Instruction::HashManyPSD4(_)) => {
763 bail!("`hash_many.psd4` is not supported in finalize")
764 }
765 Command::Instruction(Instruction::HashManyPSD8(_)) => {
766 bail!("`hash_many.psd8` is not supported in finalize")
767 }
768 Command::Instruction(Instruction::Inv(_)) => Ok(2_500),
769 Command::Instruction(Instruction::IsEq(_)) => Ok(500),
770 Command::Instruction(Instruction::IsNeq(_)) => Ok(500),
771 Command::Instruction(Instruction::LessThan(_)) => Ok(500),
772 Command::Instruction(Instruction::LessThanOrEqual(_)) => Ok(500),
773 Command::Instruction(Instruction::Modulo(_)) => Ok(500),
774 Command::Instruction(Instruction::Mul(mul)) => {
775 ensure!(mul.operands().len() == 2, "'mul' must contain exactly 2 operands");
777 match finalize_types.get_type_from_operand(stack, &mul.operands()[0])? {
779 FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Group)) => Ok(10_000),
780 FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Scalar)) => Ok(10_000),
781 FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
782 FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'mul' does not support arrays"),
783 FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
784 bail!("'mul' does not support structs")
785 }
786 FinalizeType::Future(_) => bail!("'mul' does not support futures"),
787 FinalizeType::DynamicFuture => bail!("'mul' does not support dynamic futures"),
788 }
789 }
790 Command::Instruction(Instruction::MulWrapped(_)) => Ok(500),
791 Command::Instruction(Instruction::Nand(_)) => Ok(500),
792 Command::Instruction(Instruction::Neg(_)) => Ok(500),
793 Command::Instruction(Instruction::Nor(_)) => Ok(500),
794 Command::Instruction(Instruction::Not(_)) => Ok(500),
795 Command::Instruction(Instruction::Or(_)) => Ok(500),
796 Command::Instruction(Instruction::Pow(pow)) => {
797 ensure!(!pow.operands().is_empty(), "'pow' must contain at least 1 operand");
799 match finalize_types.get_type_from_operand(stack, &pow.operands()[0])? {
801 FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
802 FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
803 FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'pow' does not support arrays"),
804 FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
805 bail!("'pow' does not support structs")
806 }
807 FinalizeType::Future(_) => bail!("'pow' does not support futures"),
808 FinalizeType::DynamicFuture => bail!("'pow' does not support dynamic futures"),
809 }
810 }
811 Command::Instruction(Instruction::PowWrapped(_)) => Ok(500),
812 Command::Instruction(Instruction::Rem(_)) => Ok(500),
813 Command::Instruction(Instruction::RemWrapped(_)) => Ok(500),
814 Command::Instruction(Instruction::SerializeBits(serialize)) => {
815 Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(serialize.destination_type().clone()))?
816 .saturating_mul(CAST_PER_BYTE_COST)
817 .saturating_add(CAST_BASE_COST))
818 }
819 Command::Instruction(Instruction::SerializeBitsRaw(serialize)) => {
820 Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(serialize.destination_type().clone()))?
821 .saturating_mul(CAST_PER_BYTE_COST)
822 .saturating_add(CAST_BASE_COST))
823 }
824 Command::Instruction(Instruction::SignVerify(sign)) => {
825 cost_in_size(stack, finalize_types, sign.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
826 }
827 Command::Instruction(Instruction::Shl(_)) => Ok(500),
828 Command::Instruction(Instruction::ShlWrapped(_)) => Ok(500),
829 Command::Instruction(Instruction::Shr(_)) => Ok(500),
830 Command::Instruction(Instruction::ShrWrapped(_)) => Ok(500),
831 Command::Instruction(Instruction::SnarkVerify(snark)) => {
832 cost_in_size(stack, finalize_types, snark.operands(), SNARK_VERIFY_PER_BYTE_COST, SNARK_VERIFY_BASE_COST)
833 }
834 Command::Instruction(Instruction::SnarkVerifyBatch(snark)) => {
835 cost_in_size(stack, finalize_types, snark.operands(), SNARK_VERIFY_PER_BYTE_COST, SNARK_VERIFY_BASE_COST)
836 }
837 Command::Instruction(Instruction::Square(_)) => Ok(500),
838 Command::Instruction(Instruction::SquareRoot(_)) => Ok(2_500),
839 Command::Instruction(Instruction::Sub(_)) => Ok(500),
840 Command::Instruction(Instruction::SubWrapped(_)) => Ok(500),
841 Command::Instruction(Instruction::Ternary(_)) => Ok(500),
842 Command::Instruction(Instruction::Xor(_)) => Ok(500),
843 Command::Await(_) => Ok(500),
844 Command::Contains(command) => {
845 cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
846 }
847 Command::ContainsDynamic(command) => {
848 cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
849 }
850 Command::Get(command) => {
851 cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
852 }
853 Command::GetDynamic(command) => {
854 cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
855 }
856 Command::GetOrUse(command) => {
857 cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
858 }
859 Command::GetOrUseDynamic(command) => {
860 cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
861 }
862 Command::RandChaCha(_) => Ok(25_000),
863 Command::Remove(_) => Ok(SET_BASE_COST),
864 Command::Set(command) => {
865 cost_in_size(stack, finalize_types, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST)
866 }
867 Command::BranchEq(_) | Command::BranchNeq(_) => Ok(500),
868 Command::Position(_) => Ok(100),
869 }
870}
871
872pub fn constructor_cost_in_microcredits_v2<N: Network>(stack: &Stack<N>) -> Result<u64> {
875 match stack.program().constructor() {
876 Some(constructor) => {
877 let constructor_types = stack.get_constructor_types()?;
879 let base_cost = constructor
881 .commands()
882 .iter()
883 .map(|command| cost_per_command(stack, &constructor_types, command, ConsensusFeeVersion::V2))
884 .try_fold(0u64, |acc, res| {
885 res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Constructor cost overflowed")))
886 })?;
887 base_cost
889 .checked_mul(N::CONSTRUCTOR_FEE_MULTIPLIER)
890 .map(|result| result / N::ARC_0005_COMPUTE_DISCOUNT)
891 .ok_or(anyhow!("Constructor cost overflowed"))
892 }
893 None => Ok(0),
894 }
895}
896
897pub fn constructor_cost_in_microcredits_v1<N: Network>(stack: &Stack<N>) -> Result<u64> {
900 match stack.program().constructor() {
901 Some(constructor) => {
902 let constructor_types = stack.get_constructor_types()?;
904 let base_cost = constructor
906 .commands()
907 .iter()
908 .map(|command| cost_per_command(stack, &constructor_types, command, ConsensusFeeVersion::V2))
909 .try_fold(0u64, |acc, res| {
910 res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Constructor cost overflowed")))
911 })?;
912 base_cost.checked_mul(N::CONSTRUCTOR_FEE_MULTIPLIER).ok_or(anyhow!("Constructor cost overflowed"))
914 }
915 None => Ok(0),
916 }
917}
918
919pub fn minimum_cost_in_microcredits_v3<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
923 minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V3)
924}
925
926fn finalize_cost_for_single_function_raw<N: Network>(
931 stack: &Stack<N>,
932 function_name: &Identifier<N>,
933 consensus_fee_version: ConsensusFeeVersion,
934) -> Result<u64> {
935 let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else {
937 return Ok(0);
938 };
939
940 let finalize_types = stack.get_finalize_types(finalize.name())?;
942
943 let mut finalize_cost = 0u64;
947 for command in finalize.commands() {
948 finalize_cost = finalize_cost
949 .checked_add(cost_per_command(stack, &finalize_types, command, consensus_fee_version)?)
950 .ok_or(anyhow!("Finalize cost overflowed"))?;
951 }
952
953 Ok(finalize_cost)
954}
955
956pub(crate) fn execution_finalize_cost<N: Network>(
960 process: &Process<N>,
961 execution: &Execution<N>,
962 consensus_fee_version: ConsensusFeeVersion,
963) -> Result<u64> {
964 let quotient = match consensus_fee_version {
968 ConsensusFeeVersion::V1 | ConsensusFeeVersion::V2 => 1,
969 ConsensusFeeVersion::V3 => N::ARC_0005_COMPUTE_DISCOUNT,
970 };
971
972 let mut total_cost = 0u64;
973
974 for transition in execution.transitions() {
977 let stack = process.get_stack(transition.program_id())?;
979 let cost = finalize_cost_for_single_function_raw(&stack, transition.function_name(), consensus_fee_version)?;
981 total_cost = total_cost.checked_add(cost).ok_or(anyhow!("Execution finalize cost overflowed"))?;
983 }
984
985 Ok(total_cost / quotient)
987}
988
989pub fn minimum_cost_in_microcredits_v2<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
993 minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V2)
994}
995
996pub fn minimum_cost_in_microcredits_v1<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
1000 minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V1)
1001}
1002
1003fn minimum_cost_in_microcredits<N: Network>(
1006 stack: &Stack<N>,
1007 function_name: &Identifier<N>,
1008 consensus_fee_version: ConsensusFeeVersion,
1009) -> Result<u64> {
1010 let mut finalize_cost = 0u64;
1012 let mut finalizes = vec![(StackRef::Internal(stack), *function_name)];
1014 let mut num_finalizes = 1;
1016 let quotient = match consensus_fee_version {
1018 ConsensusFeeVersion::V1 | ConsensusFeeVersion::V2 => 1,
1019 ConsensusFeeVersion::V3 => N::ARC_0005_COMPUTE_DISCOUNT,
1020 };
1021 while let Some((stack_ref, function_name)) = finalizes.pop() {
1023 ensure!(
1026 num_finalizes < Transaction::<N>::MAX_TRANSITIONS,
1027 "The number of finalize blocks must be less than '{}'",
1028 Transaction::<N>::MAX_TRANSITIONS
1029 );
1030 if let Some(finalize) = stack_ref.get_function_ref(&function_name)?.finalize_logic() {
1035 for input in finalize.inputs() {
1037 if let FinalizeType::Future(future) = input.finalize_type() {
1038 num_finalizes += 1;
1040 let stack = if future.program_id() == stack.program().id() {
1043 StackRef::Internal(stack)
1044 } else {
1045 StackRef::External(stack_ref.get_external_stack(future.program_id())?)
1046 };
1047 finalizes.push((stack, *future.resource()));
1049 }
1050 }
1051 let finalize_types = stack_ref.get_finalize_types(finalize.name())?;
1053 for command in finalize.commands() {
1055 finalize_cost = finalize_cost
1057 .checked_add(cost_per_command(&stack_ref, &finalize_types, command, consensus_fee_version)?)
1058 .ok_or(anyhow!("Finalize cost overflowed"))?;
1059 }
1060 }
1061 }
1062 Ok(finalize_cost / quotient)
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067 use super::*;
1068 use crate::test_helpers::get_execution;
1069 use circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0};
1070
1071 use console::{
1072 network::{CanaryV0, ConsensusVersion, MainnetV0, TestnetV0, prelude::consensus_config_value_by_version},
1073 types::Address,
1074 };
1075 use snarkvm_synthesizer_program::Program;
1076
1077 const SIZE_BOUNDARY_PROGRAM: &str = r#"
1079program size_boundary.aleo;
1080
1081function under_five_thousand:
1082 input r0 as group.public;
1083 cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
1084 cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
1085 cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
1086 output r2 as [[group; 9u32]; 10u32].public;
1087 output r3 as [group; 7u32].public;
1088
1089function over_five_thousand:
1090 input r0 as group.public;
1091 cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
1092 cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
1093 cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
1094 output r2 as [[group; 9u32]; 10u32].public;
1095 output r3 as [group; 7u32].public;
1096 output 5u64 as u64.public;
1097 "#;
1098 const STORAGE_COST_ABOVE_THRESHOLD: u64 = 5002;
1100 const V1_STORAGE_COST_MAX: u64 = 3_276_800;
1102 const V14_STORAGE_COST_MAX: u64 = 117_964_800;
1103
1104 fn test_storage_cost_bounds<N: Network>() {
1105 let threshold = N::EXECUTION_STORAGE_PENALTY_THRESHOLD;
1107 let threshold_lower_offset = threshold.saturating_sub(1);
1108 let threshold_upper_offset = threshold.saturating_add(1);
1109
1110 assert_eq!(execution_storage_cost::<N>(0), 0);
1112 assert_eq!(execution_storage_cost::<N>(1), 1);
1113 assert_eq!(execution_storage_cost::<N>(threshold_lower_offset), threshold_lower_offset);
1114 assert_eq!(execution_storage_cost::<N>(threshold), threshold);
1115 assert_eq!(execution_storage_cost::<N>(threshold_upper_offset), STORAGE_COST_ABOVE_THRESHOLD);
1116 let v1_max_tx_size = consensus_config_value_by_version!(N, MAX_TRANSACTION_SIZE, ConsensusVersion::V1).unwrap();
1118 assert_eq!(execution_storage_cost::<N>(v1_max_tx_size as u64), V1_STORAGE_COST_MAX);
1119 let v14_max_tx_size =
1120 consensus_config_value_by_version!(N, MAX_TRANSACTION_SIZE, ConsensusVersion::V14).unwrap();
1121 assert_eq!(execution_storage_cost::<N>(v14_max_tx_size as u64), V14_STORAGE_COST_MAX);
1122 }
1123
1124 #[test]
1125 fn test_storage_cost_bounds_for_all_networks() {
1126 test_storage_cost_bounds::<CanaryV0>();
1127 test_storage_cost_bounds::<MainnetV0>();
1128 test_storage_cost_bounds::<TestnetV0>();
1129 }
1130
1131 #[test]
1132 fn test_storage_costs_compute_correctly() {
1133 let threshold = MainnetV0::EXECUTION_STORAGE_PENALTY_THRESHOLD;
1135
1136 let mut process = Process::load().unwrap();
1138
1139 let program = Program::from_str(SIZE_BOUNDARY_PROGRAM).unwrap();
1141
1142 let under_5000 = Identifier::from_str("under_five_thousand").unwrap();
1144 let over_5000 = Identifier::from_str("over_five_thousand").unwrap();
1145
1146 let execution_under_5000 = get_execution(&mut process, &program, &under_5000, ["2group"].into_iter());
1148 let execution_size_under_5000 = execution_under_5000.size_in_bytes().unwrap();
1149 let (_, (storage_cost_under_5000, _)) =
1150 execution_cost_v3(&process, &execution_under_5000, execution_size_under_5000).unwrap();
1151 let execution_over_5000 = get_execution(&mut process, &program, &over_5000, ["2group"].into_iter());
1152 let execution_size_over_5000 = execution_over_5000.size_in_bytes().unwrap();
1153 let (_, (storage_cost_over_5000, _)) =
1154 execution_cost_v3(&process, &execution_over_5000, execution_size_over_5000).unwrap();
1155
1156 assert!(execution_size_under_5000 < threshold);
1158 assert!(execution_size_over_5000 > threshold);
1159
1160 assert_eq!(storage_cost_under_5000, execution_storage_cost::<MainnetV0>(execution_size_under_5000));
1162 assert_eq!(storage_cost_over_5000, execution_storage_cost::<MainnetV0>(execution_size_over_5000));
1163 }
1164
1165 #[test]
1166 fn test_deployment_cost_with_constructors() {
1167 fn run_test<N: Network, A: Aleo<Network = N>>() {
1169 let process = Process::<N>::load().unwrap();
1170 let rng = &mut TestRng::default();
1171
1172 let program_0 = Program::from_str(
1174 r"
1175program program_with_constructor.aleo;
1176
1177constructor:
1178 assert.eq true true;
1179
1180mapping foo:
1181 key as field.public;
1182 value as field.public;
1183
1184function dummy:",
1185 )
1186 .unwrap();
1187
1188 let program_1 = Program::from_str(
1189 r"
1190program program_with_constructor.aleo;
1191
1192constructor:
1193 assert.eq edition 0u16;
1194
1195mapping foo:
1196 key as field.public;
1197 value as field.public;
1198
1199function dummy:",
1200 )
1201 .unwrap();
1202
1203 let program_2 = Program::from_str(
1204 r"
1205program program_with_constructor.aleo;
1206
1207constructor:
1208 get foo[0field] into r0;
1209
1210mapping foo:
1211 key as field.public;
1212 value as field.public;
1213
1214function dummy:",
1215 )
1216 .unwrap();
1217
1218 let program_3 = Program::from_str(
1219 r"
1220program program_with_constructor.aleo;
1221
1222constructor:
1223 set 0field into foo[0field];
1224
1225mapping foo:
1226 key as field.public;
1227 value as field.public;
1228
1229function dummy:",
1230 )
1231 .unwrap();
1232
1233 let mut deployment_0 = process.deploy::<A, _>(&program_0, rng).unwrap();
1235 deployment_0.set_program_checksum_raw(Some(deployment_0.program().to_checksum()));
1236 deployment_0.set_program_owner_raw(Some(Address::rand(rng)));
1237 let expected_storage_cost = 879000;
1238 let expected_synthesis_cost = 603500;
1239 let expected_constructor_cost = 50000;
1240 let expected_namespace_cost = 1000000;
1241 let expected_total_cost =
1242 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1243 assert_eq!(
1244 deployment_cost_v1(&process, &deployment_0).unwrap(),
1245 (
1246 expected_total_cost,
1247 (
1248 expected_storage_cost,
1249 expected_synthesis_cost,
1250 expected_constructor_cost,
1251 expected_namespace_cost
1252 )
1253 )
1254 );
1255 let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1256 let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1257 let expected_total_cost =
1258 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1259 assert_eq!(
1260 deployment_cost_v2(&process, &deployment_0).unwrap(),
1261 (
1262 expected_total_cost,
1263 (
1264 expected_storage_cost,
1265 expected_synthesis_cost,
1266 expected_constructor_cost,
1267 expected_namespace_cost
1268 )
1269 )
1270 );
1271
1272 let mut deployment_1 = process.deploy::<A, _>(&program_1, rng).unwrap();
1273 deployment_1.set_program_checksum_raw(Some(deployment_1.program().to_checksum()));
1274 deployment_1.set_program_owner_raw(Some(Address::rand(rng)));
1275 let expected_storage_cost = 878000;
1276 let expected_synthesis_cost = 603500;
1277 let expected_constructor_cost = 50000;
1278 let expected_namespace_cost = 1000000;
1279 let expected_total_cost =
1280 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1281 assert_eq!(
1282 deployment_cost_v1(&process, &deployment_1).unwrap(),
1283 (
1284 expected_total_cost,
1285 (
1286 expected_storage_cost,
1287 expected_synthesis_cost,
1288 expected_constructor_cost,
1289 expected_namespace_cost
1290 )
1291 )
1292 );
1293 let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1294 let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1295 let expected_total_cost =
1296 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1297 assert_eq!(
1298 deployment_cost_v2(&process, &deployment_1).unwrap(),
1299 (
1300 expected_total_cost,
1301 (
1302 expected_storage_cost,
1303 expected_synthesis_cost,
1304 expected_constructor_cost,
1305 expected_namespace_cost
1306 )
1307 )
1308 );
1309
1310 let mut deployment_2 = process.deploy::<A, _>(&program_2, rng).unwrap();
1311 deployment_2.set_program_checksum_raw(Some(deployment_2.program().to_checksum()));
1312 deployment_2.set_program_owner_raw(Some(Address::rand(rng)));
1313 let expected_storage_cost = 911000;
1314 let expected_synthesis_cost = 603500;
1315 let expected_constructor_cost = 182000;
1316 let expected_namespace_cost = 1000000;
1317 let expected_total_cost =
1318 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1319 assert_eq!(
1320 deployment_cost_v1(&process, &deployment_2).unwrap(),
1321 (
1322 expected_total_cost,
1323 (
1324 expected_storage_cost,
1325 expected_synthesis_cost,
1326 expected_constructor_cost,
1327 expected_namespace_cost
1328 )
1329 )
1330 );
1331 let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1332 let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1333 let expected_total_cost =
1334 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1335 assert_eq!(
1336 deployment_cost_v2(&process, &deployment_2).unwrap(),
1337 (
1338 expected_total_cost,
1339 (
1340 expected_storage_cost,
1341 expected_synthesis_cost,
1342 expected_constructor_cost,
1343 expected_namespace_cost
1344 )
1345 )
1346 );
1347
1348 let mut deployment_3 = process.deploy::<A, _>(&program_3, rng).unwrap();
1349 deployment_3.set_program_checksum_raw(Some(deployment_3.program().to_checksum()));
1350 deployment_3.set_program_owner_raw(Some(Address::rand(rng)));
1351 let expected_storage_cost = 943000;
1352 let expected_synthesis_cost = 603500;
1353 let expected_constructor_cost = 1640000;
1354 let expected_namespace_cost = 1000000;
1355 let expected_total_cost =
1356 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1357 assert_eq!(
1358 deployment_cost_v1(&process, &deployment_3).unwrap(),
1359 (
1360 expected_total_cost,
1361 (
1362 expected_storage_cost,
1363 expected_synthesis_cost,
1364 expected_constructor_cost,
1365 expected_namespace_cost
1366 )
1367 )
1368 );
1369 let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1370 let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
1371 let expected_total_cost =
1372 expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
1373 assert_eq!(
1374 deployment_cost_v2(&process, &deployment_3).unwrap(),
1375 (
1376 expected_total_cost,
1377 (
1378 expected_storage_cost,
1379 expected_synthesis_cost,
1380 expected_constructor_cost,
1381 expected_namespace_cost
1382 )
1383 )
1384 );
1385 }
1386
1387 run_test::<CanaryV0, AleoCanaryV0>();
1389 run_test::<MainnetV0, AleoV0>();
1390 run_test::<TestnetV0, AleoTestnetV0>();
1391 }
1392
1393 const FINALIZE_PROGRAM: &str = r#"
1395program finalize_test.aleo;
1396
1397mapping counter:
1398 key as u64.public;
1399 value as u64.public;
1400
1401function increment:
1402 input r0 as u64.public;
1403 async increment r0 into r1;
1404 output r1 as finalize_test.aleo/increment.future;
1405
1406finalize increment:
1407 input r0 as u64.public;
1408 get.or_use counter[r0] 0u64 into r1;
1409 add r1 1u64 into r2;
1410 set r2 into counter[r0];
1411"#;
1412
1413 #[test]
1414 fn test_execution_finalize_cost_matches_static() {
1415 let mut process = Process::load().unwrap();
1420 let program = Program::from_str(FINALIZE_PROGRAM).unwrap();
1421 let function_name = Identifier::from_str("increment").unwrap();
1422
1423 let execution = get_execution(&mut process, &program, &function_name, ["42u64"].into_iter());
1425
1426 let stack = process.get_stack(program.id()).unwrap();
1428
1429 let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
1432 let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
1433 assert_eq!(
1434 static_cost_v1, runtime_cost_v1,
1435 "V1: Static and runtime costs should match: static={static_cost_v1}, runtime={runtime_cost_v1}"
1436 );
1437
1438 let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
1440 let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
1441 assert_eq!(
1442 static_cost_v2, runtime_cost_v2,
1443 "V2: Static and runtime costs should match: static={static_cost_v2}, runtime={runtime_cost_v2}"
1444 );
1445
1446 let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
1448 let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
1449 assert_eq!(
1450 static_cost_v3, runtime_cost_v3,
1451 "V3: Static and runtime costs should match: static={static_cost_v3}, runtime={runtime_cost_v3}"
1452 );
1453
1454 assert!(static_cost_v1 > 0, "Expected non-zero cost");
1456 println!("Single function - Static cost V3: {static_cost_v3}");
1457 }
1458
1459 #[test]
1460 fn test_execution_finalize_cost_matches_static_nested_calls() {
1461 let (_, child_program) = Program::<MainnetV0>::parse(
1468 r"
1469program child.aleo;
1470
1471mapping child_counter:
1472 key as u64.public;
1473 value as u64.public;
1474
1475function child_fn:
1476 input r0 as u64.public;
1477 async child_fn r0 into r1;
1478 output r1 as child.aleo/child_fn.future;
1479
1480finalize child_fn:
1481 input r0 as u64.public;
1482 get.or_use child_counter[r0] 0u64 into r1;
1483 add r1 1u64 into r2;
1484 set r2 into child_counter[r0];
1485",
1486 )
1487 .unwrap();
1488
1489 let (_, caller_program) = Program::<MainnetV0>::parse(
1491 r"
1492import child.aleo;
1493
1494program caller.aleo;
1495
1496mapping caller_counter:
1497 key as u64.public;
1498 value as u64.public;
1499
1500function call_child:
1501 input r0 as u64.public;
1502 call child.aleo/child_fn r0 into r1;
1503 async call_child r1 r0 into r2;
1504 output r2 as caller.aleo/call_child.future;
1505
1506finalize call_child:
1507 input r0 as child.aleo/child_fn.future;
1508 input r1 as u64.public;
1509 await r0;
1510 get.or_use caller_counter[r1] 0u64 into r2;
1511 add r2 10u64 into r3;
1512 set r3 into caller_counter[r1];
1513",
1514 )
1515 .unwrap();
1516
1517 let mut process = crate::test_helpers::sample_process(&child_program);
1519 process.add_program(&caller_program).unwrap();
1520
1521 let function_name = Identifier::from_str("call_child").unwrap();
1522
1523 let execution = get_execution(&mut process, &caller_program, &function_name, ["42u64"].into_iter());
1525
1526 assert_eq!(execution.transitions().count(), 2, "Expected 2 transitions for nested call");
1528
1529 let stack = process.get_stack(caller_program.id()).unwrap();
1531
1532 let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
1534 let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
1535
1536 println!("Nested calls - Static cost V3: {static_cost_v3}");
1537 println!("Nested calls - Runtime cost V3: {runtime_cost_v3}");
1538 println!("Nested calls - Number of transitions: {}", execution.transitions().count());
1539
1540 assert_eq!(
1541 static_cost_v3, runtime_cost_v3,
1542 "V3: Static and runtime costs should match for nested calls: static={static_cost_v3}, runtime={runtime_cost_v3}"
1543 );
1544
1545 let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
1547 let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
1548 assert_eq!(
1549 static_cost_v1, runtime_cost_v1,
1550 "V1: Static and runtime costs should match for nested calls: static={static_cost_v1}, runtime={runtime_cost_v1}"
1551 );
1552
1553 let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
1554 let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
1555 assert_eq!(
1556 static_cost_v2, runtime_cost_v2,
1557 "V2: Static and runtime costs should match for nested calls: static={static_cost_v2}, runtime={runtime_cost_v2}"
1558 );
1559
1560 assert!(static_cost_v3 > 0, "Expected non-zero cost for nested calls");
1562 }
1563
1564 #[test]
1565 fn test_execution_finalize_cost_matches_static_complex_call_graph() {
1566 let (_, leaf_program) = Program::<MainnetV0>::parse(
1579 r"
1580program leaf.aleo;
1581
1582mapping leaf_data:
1583 key as u64.public;
1584 value as u64.public;
1585
1586function leaf_fn:
1587 input r0 as u64.public;
1588 async leaf_fn r0 into r1;
1589 output r1 as leaf.aleo/leaf_fn.future;
1590
1591finalize leaf_fn:
1592 input r0 as u64.public;
1593 get.or_use leaf_data[r0] 0u64 into r1;
1594 add r1 1u64 into r2;
1595 set r2 into leaf_data[r0];
1596",
1597 )
1598 .unwrap();
1599
1600 let (_, level1_a_program) = Program::<MainnetV0>::parse(
1602 r"
1603import leaf.aleo;
1604
1605program level1_a.aleo;
1606
1607mapping level1_a_data:
1608 key as u64.public;
1609 value as u64.public;
1610
1611function fn_a:
1612 input r0 as u64.public;
1613 call leaf.aleo/leaf_fn r0 into r1;
1614 async fn_a r1 r0 into r2;
1615 output r2 as level1_a.aleo/fn_a.future;
1616
1617finalize fn_a:
1618 input r0 as leaf.aleo/leaf_fn.future;
1619 input r1 as u64.public;
1620 await r0;
1621 get.or_use level1_a_data[r1] 0u64 into r2;
1622 add r2 100u64 into r3;
1623 set r3 into level1_a_data[r1];
1624",
1625 )
1626 .unwrap();
1627
1628 let (_, level1_b_program) = Program::<MainnetV0>::parse(
1630 r"
1631import leaf.aleo;
1632
1633program level1_b.aleo;
1634
1635mapping level1_b_data:
1636 key as u64.public;
1637 value as u64.public;
1638
1639function fn_b:
1640 input r0 as u64.public;
1641 call leaf.aleo/leaf_fn r0 into r1;
1642 async fn_b r1 r0 into r2;
1643 output r2 as level1_b.aleo/fn_b.future;
1644
1645finalize fn_b:
1646 input r0 as leaf.aleo/leaf_fn.future;
1647 input r1 as u64.public;
1648 await r0;
1649 get.or_use level1_b_data[r1] 0u64 into r2;
1650 add r2 200u64 into r3;
1651 set r3 into level1_b_data[r1];
1652",
1653 )
1654 .unwrap();
1655
1656 let (_, root_program) = Program::<MainnetV0>::parse(
1659 r"
1660import leaf.aleo;
1661import level1_a.aleo;
1662import level1_b.aleo;
1663
1664program root.aleo;
1665
1666mapping root_data:
1667 key as u64.public;
1668 value as u64.public;
1669
1670function main:
1671 input r0 as u64.public;
1672 call level1_a.aleo/fn_a r0 into r1;
1673 call level1_b.aleo/fn_b r0 into r2;
1674 async main r1 r2 r0 into r3;
1675 output r3 as root.aleo/main.future;
1676
1677finalize main:
1678 input r0 as level1_a.aleo/fn_a.future;
1679 input r1 as level1_b.aleo/fn_b.future;
1680 input r2 as u64.public;
1681 await r0;
1682 await r1;
1683 get.or_use root_data[r2] 0u64 into r3;
1684 add r3 1000u64 into r4;
1685 set r4 into root_data[r2];
1686",
1687 )
1688 .unwrap();
1689
1690 let mut process = crate::test_helpers::sample_process(&leaf_program);
1692 process.add_program(&level1_a_program).unwrap();
1693 process.add_program(&level1_b_program).unwrap();
1694 process.add_program(&root_program).unwrap();
1695
1696 let function_name = Identifier::from_str("main").unwrap();
1697
1698 let execution = get_execution(&mut process, &root_program, &function_name, ["42u64"].into_iter());
1700
1701 let num_transitions = execution.transitions().count();
1703 println!("Complex call graph - Number of transitions: {num_transitions}");
1704 assert_eq!(num_transitions, 5, "Expected 5 transitions for complex call graph");
1705
1706 let stack = process.get_stack(root_program.id()).unwrap();
1708
1709 let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
1711 let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
1712
1713 println!("Complex call graph - Static cost V3: {static_cost_v3}");
1714 println!("Complex call graph - Runtime cost V3: {runtime_cost_v3}");
1715
1716 assert_eq!(
1717 static_cost_v3, runtime_cost_v3,
1718 "V3: Static and runtime costs should match for complex call graph: static={static_cost_v3}, runtime={runtime_cost_v3}"
1719 );
1720
1721 let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
1723 let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
1724 assert_eq!(static_cost_v1, runtime_cost_v1, "V1: Static and runtime costs should match for complex call graph");
1725
1726 let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
1727 let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
1728 assert_eq!(static_cost_v2, runtime_cost_v2, "V2: Static and runtime costs should match for complex call graph");
1729
1730 assert!(static_cost_v3 > 0, "Expected non-zero cost");
1732 println!(
1733 "Complex call graph - V1 cost: {static_cost_v1}, V2 cost: {static_cost_v2}, V3 cost: {static_cost_v3}"
1734 );
1735 }
1736}