snarkvm_synthesizer_debug/vm/helpers/
cost.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::VM;
16use console::{
17    prelude::*,
18    program::{LiteralType, PlaintextType},
19};
20use ledger_block::{Deployment, Execution};
21use ledger_store::ConsensusStorage;
22use synthesizer_program::{Command, Finalize, Instruction};
23
24use std::collections::HashMap;
25
26/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, namespace cost)).
27pub fn deployment_cost<N: Network>(deployment: &Deployment<N>) -> Result<(u64, (u64, u64))> {
28    // Determine the number of bytes in the deployment.
29    let size_in_bytes = deployment.size_in_bytes()?;
30    // Retrieve the program ID.
31    let program_id = deployment.program_id();
32    // Determine the number of characters in the program ID.
33    let num_characters = u32::try_from(program_id.name().to_string().len())?;
34
35    // Compute the storage cost in microcredits.
36    let storage_cost = size_in_bytes
37        .checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
38        .ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
39
40    // Compute the namespace cost in credits: 10^(10 - num_characters).
41    let namespace_cost = 10u64
42        .checked_pow(10u32.saturating_sub(num_characters))
43        .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
44        .saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits.
45
46    // Compute the total cost in microcredits.
47    let total_cost = storage_cost
48        .checked_add(namespace_cost)
49        .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
50
51    Ok((total_cost, (storage_cost, namespace_cost)))
52}
53
54/// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, namespace cost)).
55pub fn execution_cost<N: Network, C: ConsensusStorage<N>>(
56    vm: &VM<N, C>,
57    execution: &Execution<N>,
58) -> Result<(u64, (u64, u64))> {
59    // Compute the storage cost in microcredits.
60    let storage_cost = execution.size_in_bytes()?;
61
62    // Prepare the program lookup.
63    let lookup = execution
64        .transitions()
65        .map(|transition| {
66            let program_id = transition.program_id();
67            Ok((*program_id, vm.process().read().get_program(program_id)?.clone()))
68        })
69        .collect::<Result<HashMap<_, _>>>()?;
70
71    // Compute the finalize cost in microcredits.
72    let mut finalize_cost = 0u64;
73    // Iterate over the transitions to accumulate the finalize cost.
74    for transition in execution.transitions() {
75        // Retrieve the program ID.
76        let program_id = transition.program_id();
77        // Retrieve the function name.
78        let function_name = transition.function_name();
79        // Retrieve the program.
80        let program = lookup.get(program_id).ok_or(anyhow!("Program '{program_id}' is missing"))?;
81        // Retrieve the finalize cost.
82        let cost = match program.get_function(function_name)?.finalize_logic() {
83            Some(finalize) => cost_in_microcredits(finalize)?,
84            None => continue,
85        };
86        // Accumulate the finalize cost.
87        finalize_cost = finalize_cost
88            .checked_add(cost)
89            .ok_or(anyhow!("The finalize cost computation overflowed for an execution"))?;
90    }
91
92    // Compute the total cost in microcredits.
93    let total_cost = storage_cost
94        .checked_add(finalize_cost)
95        .ok_or(anyhow!("The total cost computation overflowed for an execution"))?;
96
97    Ok((total_cost, (storage_cost, finalize_cost)))
98}
99
100/// Returns the minimum number of microcredits required to run the finalize.
101pub fn cost_in_microcredits<N: Network>(finalize: &Finalize<N>) -> Result<u64> {
102    // Defines the cost of each command.
103    let cost = |command: &Command<N>| match command {
104        Command::Instruction(Instruction::Abs(_)) => Ok(2_000),
105        Command::Instruction(Instruction::AbsWrapped(_)) => Ok(2_000),
106        Command::Instruction(Instruction::Add(_)) => Ok(2_000),
107        Command::Instruction(Instruction::AddWrapped(_)) => Ok(2_000),
108        Command::Instruction(Instruction::And(_)) => Ok(2_000),
109        Command::Instruction(Instruction::AssertEq(_)) => Ok(2_000),
110        Command::Instruction(Instruction::AssertNeq(_)) => Ok(2_000),
111        Command::Instruction(Instruction::Async(_)) => bail!("`async` is not supported in finalize."),
112        Command::Instruction(Instruction::Call(_)) => bail!("`call` is not supported in finalize."),
113        Command::Instruction(Instruction::Cast(_)) => Ok(2_000),
114        Command::Instruction(Instruction::CastLossy(_)) => Ok(2_000),
115        Command::Instruction(Instruction::CommitBHP256(_)) => Ok(200_000),
116        Command::Instruction(Instruction::CommitBHP512(_)) => Ok(200_000),
117        Command::Instruction(Instruction::CommitBHP768(_)) => Ok(200_000),
118        Command::Instruction(Instruction::CommitBHP1024(_)) => Ok(200_000),
119        Command::Instruction(Instruction::CommitPED64(_)) => Ok(100_000),
120        Command::Instruction(Instruction::CommitPED128(_)) => Ok(100_000),
121        Command::Instruction(Instruction::Div(_)) => Ok(10_000),
122        Command::Instruction(Instruction::DivWrapped(_)) => Ok(2_000),
123        Command::Instruction(Instruction::Double(_)) => Ok(2_000),
124        Command::Instruction(Instruction::GreaterThan(_)) => Ok(2_000),
125        Command::Instruction(Instruction::GreaterThanOrEqual(_)) => Ok(2_000),
126        Command::Instruction(Instruction::HashBHP256(_)) => Ok(100_000),
127        Command::Instruction(Instruction::HashBHP512(_)) => Ok(100_000),
128        Command::Instruction(Instruction::HashBHP768(_)) => Ok(100_000),
129        Command::Instruction(Instruction::HashBHP1024(_)) => Ok(100_000),
130        Command::Instruction(Instruction::HashKeccak256(_)) => Ok(100_000),
131        Command::Instruction(Instruction::HashKeccak384(_)) => Ok(100_000),
132        Command::Instruction(Instruction::HashKeccak512(_)) => Ok(100_000),
133        Command::Instruction(Instruction::HashPED64(_)) => Ok(20_000),
134        Command::Instruction(Instruction::HashPED128(_)) => Ok(30_000),
135        Command::Instruction(Instruction::HashPSD2(hash)) => match hash.destination_type() {
136            PlaintextType::Literal(LiteralType::Address) | PlaintextType::Literal(LiteralType::Group) => Ok(600_000),
137            PlaintextType::Literal(..) => Ok(60_000),
138            plaintext_type => bail!("`hash.psd2` is not supported for plaintext type '{plaintext_type}'"),
139        },
140        Command::Instruction(Instruction::HashPSD4(hash)) => match hash.destination_type() {
141            PlaintextType::Literal(LiteralType::Address) | PlaintextType::Literal(LiteralType::Group) => Ok(700_000),
142            PlaintextType::Literal(..) => Ok(100_000),
143            plaintext_type => bail!("`hash.psd4` is not supported for plaintext type '{plaintext_type}'"),
144        },
145        Command::Instruction(Instruction::HashPSD8(hash)) => match hash.destination_type() {
146            PlaintextType::Literal(LiteralType::Address) | PlaintextType::Literal(LiteralType::Group) => Ok(800_000),
147            PlaintextType::Literal(..) => Ok(200_000),
148            plaintext_type => bail!("`hash.psd8` is not supported for plaintext type '{plaintext_type}'"),
149        },
150        Command::Instruction(Instruction::HashSha3_256(_)) => Ok(100_000),
151        Command::Instruction(Instruction::HashSha3_384(_)) => Ok(100_000),
152        Command::Instruction(Instruction::HashSha3_512(_)) => Ok(100_000),
153        Command::Instruction(Instruction::HashManyPSD2(_)) => {
154            bail!("`hash_many.psd2` is not supported in finalize.")
155        }
156        Command::Instruction(Instruction::HashManyPSD4(_)) => {
157            bail!("`hash_many.psd4` is not supported in finalize.")
158        }
159        Command::Instruction(Instruction::HashManyPSD8(_)) => {
160            bail!("`hash_many.psd8` is not supported in finalize.")
161        }
162        Command::Instruction(Instruction::Inv(_)) => Ok(10_000),
163        Command::Instruction(Instruction::IsEq(_)) => Ok(2_000),
164        Command::Instruction(Instruction::IsNeq(_)) => Ok(2_000),
165        Command::Instruction(Instruction::LessThan(_)) => Ok(2_000),
166        Command::Instruction(Instruction::LessThanOrEqual(_)) => Ok(2_000),
167        Command::Instruction(Instruction::Modulo(_)) => Ok(2_000),
168        Command::Instruction(Instruction::Mul(_)) => Ok(150_000),
169        Command::Instruction(Instruction::MulWrapped(_)) => Ok(2_000),
170        Command::Instruction(Instruction::Nand(_)) => Ok(2_000),
171        Command::Instruction(Instruction::Neg(_)) => Ok(2_000),
172        Command::Instruction(Instruction::Nor(_)) => Ok(2_000),
173        Command::Instruction(Instruction::Not(_)) => Ok(2_000),
174        Command::Instruction(Instruction::Or(_)) => Ok(2_000),
175        Command::Instruction(Instruction::Pow(_)) => Ok(20_000),
176        Command::Instruction(Instruction::PowWrapped(_)) => Ok(2_000),
177        Command::Instruction(Instruction::Rem(_)) => Ok(2_000),
178        Command::Instruction(Instruction::RemWrapped(_)) => Ok(2_000),
179        Command::Instruction(Instruction::SignVerify(_)) => Ok(250_000),
180        Command::Instruction(Instruction::Shl(_)) => Ok(2_000),
181        Command::Instruction(Instruction::ShlWrapped(_)) => Ok(2_000),
182        Command::Instruction(Instruction::Shr(_)) => Ok(2_000),
183        Command::Instruction(Instruction::ShrWrapped(_)) => Ok(2_000),
184        Command::Instruction(Instruction::Square(_)) => Ok(2_000),
185        Command::Instruction(Instruction::SquareRoot(_)) => Ok(120_000),
186        Command::Instruction(Instruction::Sub(_)) => Ok(10_000),
187        Command::Instruction(Instruction::SubWrapped(_)) => Ok(2_000),
188        Command::Instruction(Instruction::Ternary(_)) => Ok(2_000),
189        Command::Instruction(Instruction::Xor(_)) => Ok(2_000),
190        // TODO: The following 'finalize' commands are currently priced higher than expected.
191        //  Expect these numbers to change as their usage is stabilized.
192        Command::Await(_) => Ok(2_000),
193        Command::Contains(_) => Ok(12_500),
194        Command::Get(_) => Ok(25_000),
195        Command::GetOrUse(_) => Ok(25_000),
196        Command::RandChaCha(_) => Ok(25_000),
197        Command::Remove(_) => Ok(10_000),
198        Command::Set(_) => Ok(100_000),
199        Command::BranchEq(_) | Command::BranchNeq(_) => Ok(5_000),
200        Command::Position(_) => Ok(1_000),
201    };
202    finalize
203        .commands()
204        .iter()
205        .map(cost)
206        .try_fold(0u64, |acc, res| res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))))
207}