use std::collections::HashMap;
use crate::{Authorization, FinalizeTypes, Process, Stack, StackRef, StackTrait};
use console::{
prelude::*,
program::{FinalizeType, Identifier, LiteralType, PlaintextType},
};
use snarkvm_algorithms::snark::varuna::VarunaVersion;
use snarkvm_ledger_block::{Deployment, Execution, Transaction};
use snarkvm_synthesizer_program::{CallDynamic, CastType, Command, GetRecordDynamic, Instruction, Operand};
use snarkvm_synthesizer_snark::proof_size;
pub type MinimumCost = u64;
pub type StorageCost = u64;
pub type SynthesisCost = u64;
pub type ConstructorCost = u64;
pub type NamespaceCost = u64;
pub type FinalizeCost = u64;
pub type DeployCostDetails = (StorageCost, SynthesisCost, ConstructorCost, NamespaceCost);
pub type ExecuteCostDetails = (StorageCost, FinalizeCost);
pub fn deployment_cost<N: Network>(
process: &Process<N>,
deployment: &Deployment<N>,
consensus_version: ConsensusVersion,
) -> Result<(MinimumCost, DeployCostDetails)> {
if consensus_version >= ConsensusVersion::V10 {
deployment_cost_v2(process, deployment)
} else {
deployment_cost_v1(process, deployment)
}
}
pub fn execution_cost<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
consensus_version: ConsensusVersion,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
let execution_size = execution.size_in_bytes()?;
execution_cost_given_size(process, execution, execution_size, consensus_version)
}
fn execution_cost_given_size<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
execution_size: u64,
consensus_version: ConsensusVersion,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
if consensus_version >= ConsensusVersion::V10 {
execution_cost_v3(process, execution, execution_size)
} else if consensus_version >= ConsensusVersion::V2 {
execution_cost_v2(process, execution, execution_size)
} else {
execution_cost_v1(process, execution, execution_size)
}
}
pub fn execution_cost_for_authorization<N: Network>(
process: &Process<N>,
authorization: &Authorization<N>,
consensus_version: ConsensusVersion,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
ensure!(
consensus_version >= ConsensusVersion::V4,
"Execution-cost computation for authorization relies on proof-size estimation, which is only implemented for Varuna version >= V2 (consensus version >= V4)"
);
let reconstructed_execution =
Execution::from(authorization.transitions().values().cloned(), N::StateRoot::default(), None)?;
let mut circuit_frequencies = HashMap::new();
for transition in authorization.transitions().values() {
let entry =
circuit_frequencies.entry((*transition.program_id(), *transition.function_name())).or_insert(0usize);
*entry += 1;
}
let mut batch_sizes: Vec<usize> = circuit_frequencies.values().cloned().collect();
let n_input_records = Authorization::number_of_input_records(authorization.transitions().values());
if n_input_records > 0 {
batch_sizes.push(n_input_records);
}
let translations_for_transaction =
Authorization::translation_batch_sizes(process, authorization.transitions().values())?;
batch_sizes.extend(translations_for_transaction);
let hiding_mode = true;
let varuna_version = VarunaVersion::V2;
let expected_proof_size = u64::try_from(proof_size::<N>(&batch_sizes, varuna_version, hiding_mode)?)?;
let unproved_execution_size = reconstructed_execution.size_in_bytes()?;
let execution_size = unproved_execution_size.checked_add(expected_proof_size).ok_or(anyhow!(
"The execution size computation overflowed for an authorization when the proof was taken into account"
))?;
execution_cost_given_size(process, &reconstructed_execution, execution_size, consensus_version)
}
pub fn deploy_compute_cost_in_microcredits(
cost_details: DeployCostDetails,
consensus_version: ConsensusVersion,
) -> Result<u64> {
let (storage_cost, synthesis_cost, constructor_cost, _) = cost_details;
let cost_to_check = if consensus_version >= ConsensusVersion::V10 {
constructor_cost
} else {
storage_cost
.checked_add(synthesis_cost)
.and_then(|synthesis_cost| synthesis_cost.checked_add(constructor_cost))
.ok_or(anyhow!("The storage, synthesis, and constructor cost computation overflowed for a deployment"))?
};
Ok(cost_to_check)
}
pub fn execute_compute_cost_in_microcredits(
cost_details: ExecuteCostDetails,
consensus_version: ConsensusVersion,
) -> Result<u64> {
let (storage_cost, finalize_cost) = cost_details;
let cost_to_check = if consensus_version >= ConsensusVersion::V10 {
finalize_cost
} else {
storage_cost
.checked_add(finalize_cost)
.ok_or(anyhow!("The storage and finalize cost computation overflowed for an execution"))?
};
Ok(cost_to_check)
}
pub fn deployment_cost_v2<N: Network>(
process: &Process<N>,
deployment: &Deployment<N>,
) -> Result<(MinimumCost, DeployCostDetails)> {
let size_in_bytes = deployment.size_in_bytes()?;
let program_id = deployment.program_id();
let num_characters = u32::try_from(program_id.name().to_string().len())?;
let num_combined_variables = deployment.num_combined_variables()?;
let num_combined_constraints = deployment.num_combined_constraints()?;
let storage_cost = size_in_bytes
.checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
.ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER
/ N::ARC_0005_COMPUTE_DISCOUNT;
let stack = Stack::new(process, deployment.program())?;
let constructor_cost = constructor_cost_in_microcredits_v2(&stack)?;
for function in deployment.program().functions().values() {
let finalize_cost = minimum_cost_in_microcredits_v3(&stack, function.name())?;
ensure!(
finalize_cost <= N::TRANSACTION_SPEND_LIMIT[1].1,
"Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'",
function.name(),
N::TRANSACTION_SPEND_LIMIT[1].1
);
}
let namespace_cost = 10u64
.checked_pow(10u32.saturating_sub(num_characters))
.ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
.saturating_mul(1_000_000);
let minimum_cost = storage_cost
.checked_add(synthesis_cost)
.and_then(|x| x.checked_add(constructor_cost))
.and_then(|x| x.checked_add(namespace_cost))
.ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
Ok((minimum_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)))
}
pub fn deployment_cost_v1<N: Network>(
process: &Process<N>,
deployment: &Deployment<N>,
) -> Result<(MinimumCost, DeployCostDetails)> {
let size_in_bytes = deployment.size_in_bytes()?;
let program_id = deployment.program_id();
let num_characters = u32::try_from(program_id.name().to_string().len())?;
let num_combined_variables = deployment.num_combined_variables()?;
let num_combined_constraints = deployment.num_combined_constraints()?;
let storage_cost = size_in_bytes
.checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
.ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER;
let stack = Stack::new(process, deployment.program())?;
let constructor_cost = constructor_cost_in_microcredits_v1(&stack)?;
for function in deployment.program().functions().values() {
let finalize_cost = minimum_cost_in_microcredits_v2(&stack, function.name())?;
ensure!(
finalize_cost <= N::TRANSACTION_SPEND_LIMIT[0].1,
"Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'",
function.name(),
N::TRANSACTION_SPEND_LIMIT[0].1
);
}
let namespace_cost = 10u64
.checked_pow(10u32.saturating_sub(num_characters))
.ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
.saturating_mul(1_000_000);
let minimum_cost = storage_cost
.checked_add(synthesis_cost)
.and_then(|x| x.checked_add(constructor_cost))
.and_then(|x| x.checked_add(namespace_cost))
.ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
Ok((minimum_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)))
}
fn execution_cost_v3<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
execution_size: u64,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
let storage_cost = execution_storage_cost::<N>(execution_size);
let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V3)?;
let total_cost = storage_cost
.checked_add(finalize_cost)
.ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
Ok((total_cost, (storage_cost, finalize_cost)))
}
fn execution_cost_v2<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
execution_size: u64,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
let storage_cost = execution_storage_cost::<N>(execution_size);
let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V2)?;
let total_cost = storage_cost
.checked_add(finalize_cost)
.ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
Ok((total_cost, (storage_cost, finalize_cost)))
}
fn execution_cost_v1<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
execution_size: u64,
) -> Result<(MinimumCost, ExecuteCostDetails)> {
let storage_cost = execution_storage_cost::<N>(execution_size);
let finalize_cost = execution_finalize_cost(process, execution, ConsensusFeeVersion::V1)?;
let total_cost = storage_cost
.checked_add(finalize_cost)
.ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
Ok((total_cost, (storage_cost, finalize_cost)))
}
fn execution_storage_cost<N: Network>(size_in_bytes: u64) -> u64 {
if size_in_bytes > N::EXECUTION_STORAGE_PENALTY_THRESHOLD {
size_in_bytes.saturating_mul(size_in_bytes).saturating_div(N::EXECUTION_STORAGE_FEE_SCALING_FACTOR)
} else {
size_in_bytes
}
}
const CAST_BASE_COST: u64 = 500;
const CAST_PER_BYTE_COST: u64 = 30;
const HASH_BASE_COST: u64 = 10_000;
const HASH_PER_BYTE_COST: u64 = 30;
const HASH_BHP_BASE_COST: u64 = 50_000;
const HASH_BHP_PER_BYTE_COST: u64 = 300;
const HASH_PSD_BASE_COST: u64 = 40_000;
const HASH_PSD_PER_BYTE_COST: u64 = 75;
const ECDSA_VERIFY_BASE_COST: u64 = 60_000;
const ECDSA_VERIFY_ETH_BASE_COST: u64 = 75_000;
const SNARK_VERIFY_BASE_COST: u64 = 100_000;
const SNARK_VERIFY_PER_BYTE_COST: u64 = 50;
#[derive(Copy, Clone)]
pub enum ConsensusFeeVersion {
V1,
V2,
V3,
}
const MAPPING_BASE_COST_V1: u64 = 10_000;
const MAPPING_BASE_COST_V2: u64 = 1_500;
const MAPPING_PER_BYTE_COST: u64 = 10;
const SET_BASE_COST: u64 = 10_000;
const SET_PER_BYTE_COST: u64 = 100;
fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, plaintext_type: &PlaintextType<N>) -> Result<u64> {
match plaintext_type {
PlaintextType::Literal(literal_type) => Ok(literal_type.size_in_bytes::<N>() as u64),
PlaintextType::Struct(struct_name) => {
let struct_ = stack.program().get_struct(struct_name)?;
let size_of_name = struct_.name().to_bytes_le()?.len() as u64;
let size_of_members = struct_.members().iter().try_fold(0u64, |acc, (_, member_type)| {
acc.checked_add(plaintext_size_in_bytes(stack, member_type)?).ok_or(anyhow!(
"Overflowed while computing the size of the struct '{}/{struct_name}' - {member_type}",
stack.program_id()
))
})?;
Ok(size_of_name.saturating_add(size_of_members))
}
PlaintextType::ExternalStruct(locator) => {
let external_stack = stack.get_external_stack(locator.program_id())?;
plaintext_size_in_bytes(&*external_stack, &PlaintextType::Struct(*locator.resource()))
}
PlaintextType::Array(array_type) => {
let num_elements = **array_type.length() as u64;
let size_of_element = plaintext_size_in_bytes(stack, array_type.next_element_type())?;
Ok(num_elements.saturating_mul(size_of_element))
}
}
}
fn cost_in_size<'a, N: Network>(
stack: &Stack<N>,
finalize_types: &FinalizeTypes<N>,
operands: impl IntoIterator<Item = &'a Operand<N>>,
byte_multiplier: u64,
base_cost: u64,
) -> Result<u64> {
let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| {
let operand_size = match finalize_types.get_type_from_operand(stack, operand)? {
FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?,
FinalizeType::Future(future) => {
bail!("Future '{future}' is not a valid operand");
}
FinalizeType::DynamicFuture => {
bail!("Dynamic future is not a valid operand");
}
};
acc.checked_add(operand_size).ok_or(anyhow!(
"Overflowed while computing the size of the operand '{operand}' in '{}'",
stack.program_id(),
))
})?;
Ok(base_cost.saturating_add(byte_multiplier.saturating_mul(size_of_operands)))
}
pub fn cost_per_command<N: Network>(
stack: &Stack<N>,
finalize_types: &FinalizeTypes<N>,
command: &Command<N>,
consensus_fee_version: ConsensusFeeVersion,
) -> Result<u64> {
let mapping_base_cost = match consensus_fee_version {
ConsensusFeeVersion::V1 => MAPPING_BASE_COST_V1,
ConsensusFeeVersion::V2 | ConsensusFeeVersion::V3 => MAPPING_BASE_COST_V2,
};
match command {
Command::Instruction(Instruction::Abs(_)) => Ok(500),
Command::Instruction(Instruction::AbsWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Add(_)) => Ok(500),
Command::Instruction(Instruction::AddWrapped(_)) => Ok(500),
Command::Instruction(Instruction::And(_)) => Ok(500),
Command::Instruction(Instruction::AssertEq(_)) => Ok(500),
Command::Instruction(Instruction::AssertNeq(_)) => Ok(500),
Command::Instruction(Instruction::Async(_)) => bail!("'async' is not supported in finalize"),
Command::Instruction(Instruction::Call(_)) => bail!("'call' is not supported in finalize"),
Command::Instruction(Instruction::CallDynamic(_)) => {
bail!("'{}' is not supported in finalize", CallDynamic::<N>::opcode())
}
Command::Instruction(Instruction::Cast(cast)) => match cast.cast_type() {
CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST)),
CastType::GroupXCoordinate | CastType::GroupYCoordinate => Ok(500),
CastType::Record(_) => bail!("'cast' to a record is not supported in finalize"),
CastType::ExternalRecord(_) => bail!("'cast' to an external record is not supported in finalize"),
CastType::DynamicRecord => bail!("'cast' to a dynamic record is not supported in finalize"),
},
Command::Instruction(Instruction::CastLossy(cast_lossy)) => match cast_lossy.cast_type() {
CastType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
CastType::Plaintext(plaintext_type) => Ok(plaintext_size_in_bytes(stack, plaintext_type)?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST)),
CastType::GroupXCoordinate | CastType::GroupYCoordinate => Ok(500),
CastType::Record(_) => bail!("'cast.lossy' to a record is not supported in finalize"),
CastType::ExternalRecord(_) => bail!("'cast.lossy' to an external record is not supported in finalize"),
CastType::DynamicRecord => bail!("'cast.lossy' to a dynamic record is not supported in finalize"),
},
Command::Instruction(Instruction::CommitBHP256(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::CommitBHP512(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::CommitBHP768(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::CommitBHP1024(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::CommitPED64(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::CommitPED128(commit)) => {
cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::DeserializeBits(deserialize)) => {
Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(deserialize.operand_type().clone()))?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST))
}
Command::Instruction(Instruction::DeserializeBitsRaw(deserialize)) => {
Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(deserialize.operand_type().clone()))?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST))
}
Command::Instruction(Instruction::Div(div)) => {
ensure!(div.operands().len() == 2, "'div' must contain exactly 2 operands");
match finalize_types.get_type_from_operand(stack, &div.operands()[0])? {
FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'div' does not support arrays"),
FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
bail!("'div' does not support structs")
}
FinalizeType::Future(_) => bail!("'div' does not support futures"),
FinalizeType::DynamicFuture => bail!("'div' does not support dynamic futures"),
}
}
Command::Instruction(Instruction::DivWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Double(_)) => Ok(500),
Command::Instruction(Instruction::ECDSAVerifyDigest(_)) => Ok(ECDSA_VERIFY_BASE_COST),
Command::Instruction(Instruction::ECDSAVerifyDigestEth(_)) => Ok(ECDSA_VERIFY_ETH_BASE_COST),
Command::Instruction(Instruction::ECDSAVerifyKeccak256(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak256Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak256Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak384(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak384Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak384Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak512(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak512Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifyKeccak512Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_256(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_256Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_256Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_384(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_384Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_384Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_512(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_512Raw(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::ECDSAVerifySha3_512Eth(ecdsa)) => {
cost_in_size(stack, finalize_types, ecdsa.operands(), HASH_PER_BYTE_COST, ECDSA_VERIFY_ETH_BASE_COST)
}
Command::Instruction(Instruction::GetRecordDynamic(_)) => {
bail!("'{}' is not supported in finalize", GetRecordDynamic::<N>::opcode())
}
Command::Instruction(Instruction::GreaterThan(_)) => Ok(500),
Command::Instruction(Instruction::GreaterThanOrEqual(_)) => Ok(500),
Command::Instruction(Instruction::HashBHP256(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP256Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP512(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP512Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP768(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP768Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP1024(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashBHP1024Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak256(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak256Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak256Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak256NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak384(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak384Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak384Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak384NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak512(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak512Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak512Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashKeccak512NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashPED64(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashPED64Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashPED128(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashPED128Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashPSD2(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashPSD2Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashPSD4(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashPSD4Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashPSD8(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashPSD8Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_256(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_256Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_256Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_256NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_384(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_384Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_384Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_384NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_512(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_512Raw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_512Native(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashSha3_512NativeRaw(hash)) => {
cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST)
}
Command::Instruction(Instruction::HashManyPSD2(_)) => {
bail!("`hash_many.psd2` is not supported in finalize")
}
Command::Instruction(Instruction::HashManyPSD4(_)) => {
bail!("`hash_many.psd4` is not supported in finalize")
}
Command::Instruction(Instruction::HashManyPSD8(_)) => {
bail!("`hash_many.psd8` is not supported in finalize")
}
Command::Instruction(Instruction::Inv(_)) => Ok(2_500),
Command::Instruction(Instruction::IsEq(_)) => Ok(500),
Command::Instruction(Instruction::IsNeq(_)) => Ok(500),
Command::Instruction(Instruction::LessThan(_)) => Ok(500),
Command::Instruction(Instruction::LessThanOrEqual(_)) => Ok(500),
Command::Instruction(Instruction::Modulo(_)) => Ok(500),
Command::Instruction(Instruction::Mul(mul)) => {
ensure!(mul.operands().len() == 2, "'mul' must contain exactly 2 operands");
match finalize_types.get_type_from_operand(stack, &mul.operands()[0])? {
FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Group)) => Ok(10_000),
FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Scalar)) => Ok(10_000),
FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'mul' does not support arrays"),
FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
bail!("'mul' does not support structs")
}
FinalizeType::Future(_) => bail!("'mul' does not support futures"),
FinalizeType::DynamicFuture => bail!("'mul' does not support dynamic futures"),
}
}
Command::Instruction(Instruction::MulWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Nand(_)) => Ok(500),
Command::Instruction(Instruction::Neg(_)) => Ok(500),
Command::Instruction(Instruction::Nor(_)) => Ok(500),
Command::Instruction(Instruction::Not(_)) => Ok(500),
Command::Instruction(Instruction::Or(_)) => Ok(500),
Command::Instruction(Instruction::Pow(pow)) => {
ensure!(!pow.operands().is_empty(), "'pow' must contain at least 1 operand");
match finalize_types.get_type_from_operand(stack, &pow.operands()[0])? {
FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500),
FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500),
FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'pow' does not support arrays"),
FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => {
bail!("'pow' does not support structs")
}
FinalizeType::Future(_) => bail!("'pow' does not support futures"),
FinalizeType::DynamicFuture => bail!("'pow' does not support dynamic futures"),
}
}
Command::Instruction(Instruction::PowWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Rem(_)) => Ok(500),
Command::Instruction(Instruction::RemWrapped(_)) => Ok(500),
Command::Instruction(Instruction::SerializeBits(serialize)) => {
Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(serialize.destination_type().clone()))?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST))
}
Command::Instruction(Instruction::SerializeBitsRaw(serialize)) => {
Ok(plaintext_size_in_bytes(stack, &PlaintextType::Array(serialize.destination_type().clone()))?
.saturating_mul(CAST_PER_BYTE_COST)
.saturating_add(CAST_BASE_COST))
}
Command::Instruction(Instruction::SignVerify(sign)) => {
cost_in_size(stack, finalize_types, sign.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST)
}
Command::Instruction(Instruction::Shl(_)) => Ok(500),
Command::Instruction(Instruction::ShlWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Shr(_)) => Ok(500),
Command::Instruction(Instruction::ShrWrapped(_)) => Ok(500),
Command::Instruction(Instruction::SnarkVerify(snark)) => {
cost_in_size(stack, finalize_types, snark.operands(), SNARK_VERIFY_PER_BYTE_COST, SNARK_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::SnarkVerifyBatch(snark)) => {
cost_in_size(stack, finalize_types, snark.operands(), SNARK_VERIFY_PER_BYTE_COST, SNARK_VERIFY_BASE_COST)
}
Command::Instruction(Instruction::Square(_)) => Ok(500),
Command::Instruction(Instruction::SquareRoot(_)) => Ok(2_500),
Command::Instruction(Instruction::Sub(_)) => Ok(500),
Command::Instruction(Instruction::SubWrapped(_)) => Ok(500),
Command::Instruction(Instruction::Ternary(_)) => Ok(500),
Command::Instruction(Instruction::Xor(_)) => Ok(500),
Command::Await(_) => Ok(500),
Command::Contains(command) => {
cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::ContainsDynamic(command) => {
cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::Get(command) => {
cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::GetDynamic(command) => {
cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::GetOrUse(command) => {
cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::GetOrUseDynamic(command) => {
cost_in_size(stack, finalize_types, [command.key_operand()], MAPPING_PER_BYTE_COST, mapping_base_cost)
}
Command::RandChaCha(_) => Ok(25_000),
Command::Remove(_) => Ok(SET_BASE_COST),
Command::Set(command) => {
cost_in_size(stack, finalize_types, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST)
}
Command::BranchEq(_) | Command::BranchNeq(_) => Ok(500),
Command::Position(_) => Ok(100),
}
}
pub fn constructor_cost_in_microcredits_v2<N: Network>(stack: &Stack<N>) -> Result<u64> {
match stack.program().constructor() {
Some(constructor) => {
let constructor_types = stack.get_constructor_types()?;
let base_cost = constructor
.commands()
.iter()
.map(|command| cost_per_command(stack, &constructor_types, command, ConsensusFeeVersion::V2))
.try_fold(0u64, |acc, res| {
res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Constructor cost overflowed")))
})?;
base_cost
.checked_mul(N::CONSTRUCTOR_FEE_MULTIPLIER)
.map(|result| result / N::ARC_0005_COMPUTE_DISCOUNT)
.ok_or(anyhow!("Constructor cost overflowed"))
}
None => Ok(0),
}
}
pub fn constructor_cost_in_microcredits_v1<N: Network>(stack: &Stack<N>) -> Result<u64> {
match stack.program().constructor() {
Some(constructor) => {
let constructor_types = stack.get_constructor_types()?;
let base_cost = constructor
.commands()
.iter()
.map(|command| cost_per_command(stack, &constructor_types, command, ConsensusFeeVersion::V2))
.try_fold(0u64, |acc, res| {
res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Constructor cost overflowed")))
})?;
base_cost.checked_mul(N::CONSTRUCTOR_FEE_MULTIPLIER).ok_or(anyhow!("Constructor cost overflowed"))
}
None => Ok(0),
}
}
pub fn minimum_cost_in_microcredits_v3<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V3)
}
fn finalize_cost_for_single_function_raw<N: Network>(
stack: &Stack<N>,
function_name: &Identifier<N>,
consensus_fee_version: ConsensusFeeVersion,
) -> Result<u64> {
let Some(finalize) = stack.get_function_ref(function_name)?.finalize_logic() else {
return Ok(0);
};
let finalize_types = stack.get_finalize_types(finalize.name())?;
let mut finalize_cost = 0u64;
for command in finalize.commands() {
finalize_cost = finalize_cost
.checked_add(cost_per_command(stack, &finalize_types, command, consensus_fee_version)?)
.ok_or(anyhow!("Finalize cost overflowed"))?;
}
Ok(finalize_cost)
}
pub(crate) fn execution_finalize_cost<N: Network>(
process: &Process<N>,
execution: &Execution<N>,
consensus_fee_version: ConsensusFeeVersion,
) -> Result<u64> {
let quotient = match consensus_fee_version {
ConsensusFeeVersion::V1 | ConsensusFeeVersion::V2 => 1,
ConsensusFeeVersion::V3 => N::ARC_0005_COMPUTE_DISCOUNT,
};
let mut total_cost = 0u64;
for transition in execution.transitions() {
let stack = process.get_stack(transition.program_id())?;
let cost = finalize_cost_for_single_function_raw(&stack, transition.function_name(), consensus_fee_version)?;
total_cost = total_cost.checked_add(cost).ok_or(anyhow!("Execution finalize cost overflowed"))?;
}
Ok(total_cost / quotient)
}
pub fn minimum_cost_in_microcredits_v2<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V2)
}
pub fn minimum_cost_in_microcredits_v1<N: Network>(stack: &Stack<N>, function_name: &Identifier<N>) -> Result<u64> {
minimum_cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V1)
}
fn minimum_cost_in_microcredits<N: Network>(
stack: &Stack<N>,
function_name: &Identifier<N>,
consensus_fee_version: ConsensusFeeVersion,
) -> Result<u64> {
let mut finalize_cost = 0u64;
let mut finalizes = vec![(StackRef::Internal(stack), *function_name)];
let mut num_finalizes = 1;
let quotient = match consensus_fee_version {
ConsensusFeeVersion::V1 | ConsensusFeeVersion::V2 => 1,
ConsensusFeeVersion::V3 => N::ARC_0005_COMPUTE_DISCOUNT,
};
while let Some((stack_ref, function_name)) = finalizes.pop() {
ensure!(
num_finalizes < Transaction::<N>::MAX_TRANSITIONS,
"The number of finalize blocks must be less than '{}'",
Transaction::<N>::MAX_TRANSITIONS
);
if let Some(finalize) = stack_ref.get_function_ref(&function_name)?.finalize_logic() {
for input in finalize.inputs() {
if let FinalizeType::Future(future) = input.finalize_type() {
num_finalizes += 1;
let stack = if future.program_id() == stack.program().id() {
StackRef::Internal(stack)
} else {
StackRef::External(stack_ref.get_external_stack(future.program_id())?)
};
finalizes.push((stack, *future.resource()));
}
}
let finalize_types = stack_ref.get_finalize_types(finalize.name())?;
for command in finalize.commands() {
finalize_cost = finalize_cost
.checked_add(cost_per_command(&stack_ref, &finalize_types, command, consensus_fee_version)?)
.ok_or(anyhow!("Finalize cost overflowed"))?;
}
}
}
Ok(finalize_cost / quotient)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::get_execution;
use circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0};
use console::{
network::{CanaryV0, ConsensusVersion, MainnetV0, TestnetV0, prelude::consensus_config_value_by_version},
types::Address,
};
use snarkvm_synthesizer_program::Program;
const SIZE_BOUNDARY_PROGRAM: &str = r#"
program size_boundary.aleo;
function under_five_thousand:
input r0 as group.public;
cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
output r2 as [[group; 9u32]; 10u32].public;
output r3 as [group; 7u32].public;
function over_five_thousand:
input r0 as group.public;
cast r0 r0 r0 r0 r0 r0 r0 r0 r0 into r1 as [group; 9u32];
cast r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 into r2 as [[group; 9u32]; 10u32];
cast r0 r0 r0 r0 r0 r0 r0 into r3 as [group; 7u32];
output r2 as [[group; 9u32]; 10u32].public;
output r3 as [group; 7u32].public;
output 5u64 as u64.public;
"#;
const STORAGE_COST_ABOVE_THRESHOLD: u64 = 5002;
const V1_STORAGE_COST_MAX: u64 = 3_276_800;
const V14_STORAGE_COST_MAX: u64 = 117_964_800;
fn test_storage_cost_bounds<N: Network>() {
let threshold = N::EXECUTION_STORAGE_PENALTY_THRESHOLD;
let threshold_lower_offset = threshold.saturating_sub(1);
let threshold_upper_offset = threshold.saturating_add(1);
assert_eq!(execution_storage_cost::<N>(0), 0);
assert_eq!(execution_storage_cost::<N>(1), 1);
assert_eq!(execution_storage_cost::<N>(threshold_lower_offset), threshold_lower_offset);
assert_eq!(execution_storage_cost::<N>(threshold), threshold);
assert_eq!(execution_storage_cost::<N>(threshold_upper_offset), STORAGE_COST_ABOVE_THRESHOLD);
let v1_max_tx_size = consensus_config_value_by_version!(N, MAX_TRANSACTION_SIZE, ConsensusVersion::V1).unwrap();
assert_eq!(execution_storage_cost::<N>(v1_max_tx_size as u64), V1_STORAGE_COST_MAX);
let v14_max_tx_size =
consensus_config_value_by_version!(N, MAX_TRANSACTION_SIZE, ConsensusVersion::V14).unwrap();
assert_eq!(execution_storage_cost::<N>(v14_max_tx_size as u64), V14_STORAGE_COST_MAX);
}
#[test]
fn test_storage_cost_bounds_for_all_networks() {
test_storage_cost_bounds::<CanaryV0>();
test_storage_cost_bounds::<MainnetV0>();
test_storage_cost_bounds::<TestnetV0>();
}
#[test]
fn test_storage_costs_compute_correctly() {
let threshold = MainnetV0::EXECUTION_STORAGE_PENALTY_THRESHOLD;
let mut process = Process::load().unwrap();
let program = Program::from_str(SIZE_BOUNDARY_PROGRAM).unwrap();
let under_5000 = Identifier::from_str("under_five_thousand").unwrap();
let over_5000 = Identifier::from_str("over_five_thousand").unwrap();
let execution_under_5000 = get_execution(&mut process, &program, &under_5000, ["2group"].into_iter());
let execution_size_under_5000 = execution_under_5000.size_in_bytes().unwrap();
let (_, (storage_cost_under_5000, _)) =
execution_cost_v3(&process, &execution_under_5000, execution_size_under_5000).unwrap();
let execution_over_5000 = get_execution(&mut process, &program, &over_5000, ["2group"].into_iter());
let execution_size_over_5000 = execution_over_5000.size_in_bytes().unwrap();
let (_, (storage_cost_over_5000, _)) =
execution_cost_v3(&process, &execution_over_5000, execution_size_over_5000).unwrap();
assert!(execution_size_under_5000 < threshold);
assert!(execution_size_over_5000 > threshold);
assert_eq!(storage_cost_under_5000, execution_storage_cost::<MainnetV0>(execution_size_under_5000));
assert_eq!(storage_cost_over_5000, execution_storage_cost::<MainnetV0>(execution_size_over_5000));
}
#[test]
fn test_deployment_cost_with_constructors() {
fn run_test<N: Network, A: Aleo<Network = N>>() {
let process = Process::<N>::load().unwrap();
let rng = &mut TestRng::default();
let program_0 = Program::from_str(
r"
program program_with_constructor.aleo;
constructor:
assert.eq true true;
mapping foo:
key as field.public;
value as field.public;
function dummy:",
)
.unwrap();
let program_1 = Program::from_str(
r"
program program_with_constructor.aleo;
constructor:
assert.eq edition 0u16;
mapping foo:
key as field.public;
value as field.public;
function dummy:",
)
.unwrap();
let program_2 = Program::from_str(
r"
program program_with_constructor.aleo;
constructor:
get foo[0field] into r0;
mapping foo:
key as field.public;
value as field.public;
function dummy:",
)
.unwrap();
let program_3 = Program::from_str(
r"
program program_with_constructor.aleo;
constructor:
set 0field into foo[0field];
mapping foo:
key as field.public;
value as field.public;
function dummy:",
)
.unwrap();
let mut deployment_0 = process.deploy::<A, _>(&program_0, rng).unwrap();
deployment_0.set_program_checksum_raw(Some(deployment_0.program().to_checksum()));
deployment_0.set_program_owner_raw(Some(Address::rand(rng)));
let expected_storage_cost = 879000;
let expected_synthesis_cost = 603500;
let expected_constructor_cost = 50000;
let expected_namespace_cost = 1000000;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v1(&process, &deployment_0).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v2(&process, &deployment_0).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let mut deployment_1 = process.deploy::<A, _>(&program_1, rng).unwrap();
deployment_1.set_program_checksum_raw(Some(deployment_1.program().to_checksum()));
deployment_1.set_program_owner_raw(Some(Address::rand(rng)));
let expected_storage_cost = 878000;
let expected_synthesis_cost = 603500;
let expected_constructor_cost = 50000;
let expected_namespace_cost = 1000000;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v1(&process, &deployment_1).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v2(&process, &deployment_1).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let mut deployment_2 = process.deploy::<A, _>(&program_2, rng).unwrap();
deployment_2.set_program_checksum_raw(Some(deployment_2.program().to_checksum()));
deployment_2.set_program_owner_raw(Some(Address::rand(rng)));
let expected_storage_cost = 911000;
let expected_synthesis_cost = 603500;
let expected_constructor_cost = 182000;
let expected_namespace_cost = 1000000;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v1(&process, &deployment_2).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v2(&process, &deployment_2).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let mut deployment_3 = process.deploy::<A, _>(&program_3, rng).unwrap();
deployment_3.set_program_checksum_raw(Some(deployment_3.program().to_checksum()));
deployment_3.set_program_owner_raw(Some(Address::rand(rng)));
let expected_storage_cost = 943000;
let expected_synthesis_cost = 603500;
let expected_constructor_cost = 1640000;
let expected_namespace_cost = 1000000;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v1(&process, &deployment_3).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
let expected_synthesis_cost = expected_synthesis_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_constructor_cost = expected_constructor_cost / N::ARC_0005_COMPUTE_DISCOUNT;
let expected_total_cost =
expected_storage_cost + expected_synthesis_cost + expected_constructor_cost + expected_namespace_cost;
assert_eq!(
deployment_cost_v2(&process, &deployment_3).unwrap(),
(
expected_total_cost,
(
expected_storage_cost,
expected_synthesis_cost,
expected_constructor_cost,
expected_namespace_cost
)
)
);
}
run_test::<CanaryV0, AleoCanaryV0>();
run_test::<MainnetV0, AleoV0>();
run_test::<TestnetV0, AleoTestnetV0>();
}
const FINALIZE_PROGRAM: &str = r#"
program finalize_test.aleo;
mapping counter:
key as u64.public;
value as u64.public;
function increment:
input r0 as u64.public;
async increment r0 into r1;
output r1 as finalize_test.aleo/increment.future;
finalize increment:
input r0 as u64.public;
get.or_use counter[r0] 0u64 into r1;
add r1 1u64 into r2;
set r2 into counter[r0];
"#;
#[test]
fn test_execution_finalize_cost_matches_static() {
let mut process = Process::load().unwrap();
let program = Program::from_str(FINALIZE_PROGRAM).unwrap();
let function_name = Identifier::from_str("increment").unwrap();
let execution = get_execution(&mut process, &program, &function_name, ["42u64"].into_iter());
let stack = process.get_stack(program.id()).unwrap();
let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
assert_eq!(
static_cost_v1, runtime_cost_v1,
"V1: Static and runtime costs should match: static={static_cost_v1}, runtime={runtime_cost_v1}"
);
let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
assert_eq!(
static_cost_v2, runtime_cost_v2,
"V2: Static and runtime costs should match: static={static_cost_v2}, runtime={runtime_cost_v2}"
);
let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
assert_eq!(
static_cost_v3, runtime_cost_v3,
"V3: Static and runtime costs should match: static={static_cost_v3}, runtime={runtime_cost_v3}"
);
assert!(static_cost_v1 > 0, "Expected non-zero cost");
println!("Single function - Static cost V3: {static_cost_v3}");
}
#[test]
fn test_execution_finalize_cost_matches_static_nested_calls() {
let (_, child_program) = Program::<MainnetV0>::parse(
r"
program child.aleo;
mapping child_counter:
key as u64.public;
value as u64.public;
function child_fn:
input r0 as u64.public;
async child_fn r0 into r1;
output r1 as child.aleo/child_fn.future;
finalize child_fn:
input r0 as u64.public;
get.or_use child_counter[r0] 0u64 into r1;
add r1 1u64 into r2;
set r2 into child_counter[r0];
",
)
.unwrap();
let (_, caller_program) = Program::<MainnetV0>::parse(
r"
import child.aleo;
program caller.aleo;
mapping caller_counter:
key as u64.public;
value as u64.public;
function call_child:
input r0 as u64.public;
call child.aleo/child_fn r0 into r1;
async call_child r1 r0 into r2;
output r2 as caller.aleo/call_child.future;
finalize call_child:
input r0 as child.aleo/child_fn.future;
input r1 as u64.public;
await r0;
get.or_use caller_counter[r1] 0u64 into r2;
add r2 10u64 into r3;
set r3 into caller_counter[r1];
",
)
.unwrap();
let mut process = crate::test_helpers::sample_process(&child_program);
process.add_program(&caller_program).unwrap();
let function_name = Identifier::from_str("call_child").unwrap();
let execution = get_execution(&mut process, &caller_program, &function_name, ["42u64"].into_iter());
assert_eq!(execution.transitions().count(), 2, "Expected 2 transitions for nested call");
let stack = process.get_stack(caller_program.id()).unwrap();
let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
println!("Nested calls - Static cost V3: {static_cost_v3}");
println!("Nested calls - Runtime cost V3: {runtime_cost_v3}");
println!("Nested calls - Number of transitions: {}", execution.transitions().count());
assert_eq!(
static_cost_v3, runtime_cost_v3,
"V3: Static and runtime costs should match for nested calls: static={static_cost_v3}, runtime={runtime_cost_v3}"
);
let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
assert_eq!(
static_cost_v1, runtime_cost_v1,
"V1: Static and runtime costs should match for nested calls: static={static_cost_v1}, runtime={runtime_cost_v1}"
);
let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
assert_eq!(
static_cost_v2, runtime_cost_v2,
"V2: Static and runtime costs should match for nested calls: static={static_cost_v2}, runtime={runtime_cost_v2}"
);
assert!(static_cost_v3 > 0, "Expected non-zero cost for nested calls");
}
#[test]
fn test_execution_finalize_cost_matches_static_complex_call_graph() {
let (_, leaf_program) = Program::<MainnetV0>::parse(
r"
program leaf.aleo;
mapping leaf_data:
key as u64.public;
value as u64.public;
function leaf_fn:
input r0 as u64.public;
async leaf_fn r0 into r1;
output r1 as leaf.aleo/leaf_fn.future;
finalize leaf_fn:
input r0 as u64.public;
get.or_use leaf_data[r0] 0u64 into r1;
add r1 1u64 into r2;
set r2 into leaf_data[r0];
",
)
.unwrap();
let (_, level1_a_program) = Program::<MainnetV0>::parse(
r"
import leaf.aleo;
program level1_a.aleo;
mapping level1_a_data:
key as u64.public;
value as u64.public;
function fn_a:
input r0 as u64.public;
call leaf.aleo/leaf_fn r0 into r1;
async fn_a r1 r0 into r2;
output r2 as level1_a.aleo/fn_a.future;
finalize fn_a:
input r0 as leaf.aleo/leaf_fn.future;
input r1 as u64.public;
await r0;
get.or_use level1_a_data[r1] 0u64 into r2;
add r2 100u64 into r3;
set r3 into level1_a_data[r1];
",
)
.unwrap();
let (_, level1_b_program) = Program::<MainnetV0>::parse(
r"
import leaf.aleo;
program level1_b.aleo;
mapping level1_b_data:
key as u64.public;
value as u64.public;
function fn_b:
input r0 as u64.public;
call leaf.aleo/leaf_fn r0 into r1;
async fn_b r1 r0 into r2;
output r2 as level1_b.aleo/fn_b.future;
finalize fn_b:
input r0 as leaf.aleo/leaf_fn.future;
input r1 as u64.public;
await r0;
get.or_use level1_b_data[r1] 0u64 into r2;
add r2 200u64 into r3;
set r3 into level1_b_data[r1];
",
)
.unwrap();
let (_, root_program) = Program::<MainnetV0>::parse(
r"
import leaf.aleo;
import level1_a.aleo;
import level1_b.aleo;
program root.aleo;
mapping root_data:
key as u64.public;
value as u64.public;
function main:
input r0 as u64.public;
call level1_a.aleo/fn_a r0 into r1;
call level1_b.aleo/fn_b r0 into r2;
async main r1 r2 r0 into r3;
output r3 as root.aleo/main.future;
finalize main:
input r0 as level1_a.aleo/fn_a.future;
input r1 as level1_b.aleo/fn_b.future;
input r2 as u64.public;
await r0;
await r1;
get.or_use root_data[r2] 0u64 into r3;
add r3 1000u64 into r4;
set r4 into root_data[r2];
",
)
.unwrap();
let mut process = crate::test_helpers::sample_process(&leaf_program);
process.add_program(&level1_a_program).unwrap();
process.add_program(&level1_b_program).unwrap();
process.add_program(&root_program).unwrap();
let function_name = Identifier::from_str("main").unwrap();
let execution = get_execution(&mut process, &root_program, &function_name, ["42u64"].into_iter());
let num_transitions = execution.transitions().count();
println!("Complex call graph - Number of transitions: {num_transitions}");
assert_eq!(num_transitions, 5, "Expected 5 transitions for complex call graph");
let stack = process.get_stack(root_program.id()).unwrap();
let static_cost_v3 = minimum_cost_in_microcredits_v3(&stack, &function_name).unwrap();
let runtime_cost_v3 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V3).unwrap();
println!("Complex call graph - Static cost V3: {static_cost_v3}");
println!("Complex call graph - Runtime cost V3: {runtime_cost_v3}");
assert_eq!(
static_cost_v3, runtime_cost_v3,
"V3: Static and runtime costs should match for complex call graph: static={static_cost_v3}, runtime={runtime_cost_v3}"
);
let static_cost_v1 = minimum_cost_in_microcredits_v1(&stack, &function_name).unwrap();
let runtime_cost_v1 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V1).unwrap();
assert_eq!(static_cost_v1, runtime_cost_v1, "V1: Static and runtime costs should match for complex call graph");
let static_cost_v2 = minimum_cost_in_microcredits_v2(&stack, &function_name).unwrap();
let runtime_cost_v2 = execution_finalize_cost(&process, &execution, ConsensusFeeVersion::V2).unwrap();
assert_eq!(static_cost_v2, runtime_cost_v2, "V2: Static and runtime costs should match for complex call graph");
assert!(static_cost_v3 > 0, "Expected non-zero cost");
println!(
"Complex call graph - V1 cost: {static_cost_v1}, V2 cost: {static_cost_v2}, V3 cost: {static_cost_v3}"
);
}
}