snarkvm_ledger_block/transaction/fee/
mod.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod bytes;
17mod serialize;
18mod string;
19
20use crate::{Input, Output, Transition};
21use console::{
22    network::prelude::*,
23    program::{Argument, Literal, Plaintext},
24    types::{Address, Field, U64},
25};
26use synthesizer_snark::Proof;
27
28#[derive(Clone, PartialEq, Eq)]
29pub struct Fee<N: Network> {
30    /// The transition.
31    transition: Transition<N>,
32    /// The global state root.
33    global_state_root: N::StateRoot,
34    /// The proof.
35    proof: Option<Proof<N>>,
36}
37
38impl<N: Network> Fee<N> {
39    /// Initializes a new `Fee` instance with the given transition, global state root, and proof.
40    pub fn from(transition: Transition<N>, global_state_root: N::StateRoot, proof: Option<Proof<N>>) -> Result<Self> {
41        // Ensure the transition is correct for a fee function.
42        match transition.is_fee_private() || transition.is_fee_public() {
43            true => Ok(Self::from_unchecked(transition, global_state_root, proof)),
44            false => bail!("Invalid fee transition locator"),
45        }
46    }
47
48    /// Initializes a new `Fee` instance with the given transition, global state root, and proof.
49    pub const fn from_unchecked(
50        transition: Transition<N>,
51        global_state_root: N::StateRoot,
52        proof: Option<Proof<N>>,
53    ) -> Self {
54        Self { transition, global_state_root, proof }
55    }
56}
57
58impl<N: Network> Fee<N> {
59    /// Returns `true` if this is a `fee_private` transition.
60    #[inline]
61    pub fn is_fee_private(&self) -> bool {
62        self.transition.is_fee_private()
63    }
64
65    /// Returns `true` if this is a `fee_public` transition.
66    #[inline]
67    pub fn is_fee_public(&self) -> bool {
68        self.transition.is_fee_public()
69    }
70}
71
72impl<N: Network> Fee<N> {
73    /// Returns 'true' if the fee amount is zero.
74    pub fn is_zero(&self) -> Result<bool> {
75        self.amount().map(|amount| amount.is_zero())
76    }
77
78    /// Returns the payer, if the fee is public.
79    pub fn payer(&self) -> Option<Address<N>> {
80        // Retrieve the payer.
81        match self.transition.outputs().last() {
82            Some(Output::Future(_, Some(future))) => match future.arguments().first() {
83                Some(Argument::Plaintext(Plaintext::Literal(Literal::Address(address), _))) => Some(*address),
84                _ => None,
85            },
86            _ => None,
87        }
88    }
89
90    /// Returns the amount (in microcredits).
91    pub fn amount(&self) -> Result<U64<N>> {
92        // Retrieve the base fee amount.
93        let base_fee_amount = self.base_amount()?;
94        // Retrieve the priority fee amount.
95        let priority_fee_amount = self.priority_amount()?;
96        // Return the amount.
97        // Note: Use of saturating add is safe, as the sum cannot exceed a u64.
98        Ok(U64::new(base_fee_amount.saturating_add(*priority_fee_amount)))
99    }
100
101    /// Returns the base amount (in microcredits).
102    pub fn base_amount(&self) -> Result<U64<N>> {
103        // Determine the indexes for the base fee.
104        // Note: Checking whether the `output` is a `Record` or `Future` is a faster way to determine if the fee is private or public respectively.
105        let base_fee_index: usize = match self.transition.outputs().last() {
106            Some(output) => match output {
107                Output::Record(..) => 1,
108                Output::Future(..) => 0,
109                _ => bail!("Unexpected output in fee transition"),
110            },
111            None => bail!("Missing output in fee transition"),
112        };
113        // Retrieve the base fee (in microcredits) as a plaintext value.
114        match self.transition.inputs().get(base_fee_index) {
115            Some(Input::Public(_, Some(Plaintext::Literal(Literal::U64(microcredits), _)))) => Ok(*microcredits),
116            _ => bail!("Failed to retrieve the base fee (in microcredits) from the fee transition"),
117        }
118    }
119
120    /// Returns the base amount (in microcredits).
121    pub fn priority_amount(&self) -> Result<U64<N>> {
122        // Determine the indexes for the priority fee.
123        // Note: Checking whether the `output` is a `Record` or `Future` is a faster way to determine if the fee is private or public respectively.
124        let priority_fee_index: usize = match self.transition.outputs().last() {
125            Some(output) => match output {
126                Output::Record(..) => 2,
127                Output::Future(..) => 1,
128                _ => bail!("Unexpected output in fee transition"),
129            },
130            None => bail!("Missing output in fee transition"),
131        };
132        // Retrieve the priority fee (in microcredits) as a plaintext value.
133        match self.transition.inputs().get(priority_fee_index) {
134            Some(Input::Public(_, Some(Plaintext::Literal(Literal::U64(microcredits), _)))) => Ok(*microcredits),
135            _ => bail!("Failed to retrieve the priority fee (in microcredits) from the fee transition"),
136        }
137    }
138
139    /// Returns the deployment or execution ID.
140    pub fn deployment_or_execution_id(&self) -> Result<Field<N>> {
141        // Determine the input index for the deployment or execution ID.
142        // Note: Checking whether the `output` is a `Record` or `Future` is a faster way to determine if the fee is private or public respectively.
143        let input_index = match self.transition.outputs().last() {
144            Some(output) => match output {
145                Output::Record(..) => 3,
146                Output::Future(..) => 2,
147                _ => bail!("Unexpected output in fee transition"),
148            },
149            None => bail!("Missing output in fee transition"),
150        };
151        // Retrieve the deployment or execution ID as a plaintext value.
152        match self.transition.inputs().get(input_index) {
153            Some(Input::Public(_, Some(Plaintext::Literal(Literal::Field(id), _)))) => Ok(*id),
154            _ => bail!("Failed to retrieve the deployment or execution ID from the fee transition"),
155        }
156    }
157
158    /// Returns the number of finalize operations.
159    pub fn num_finalize_operations(&self) -> usize {
160        // These values are empirically determined for performance.
161        match self.is_fee_public() {
162            true => 1,
163            false => 0,
164        }
165    }
166
167    /// Returns the transition ID.
168    pub fn transition_id(&self) -> &N::TransitionID {
169        self.transition.id()
170    }
171
172    /// Returns the transition.
173    pub const fn transition(&self) -> &Transition<N> {
174        &self.transition
175    }
176
177    /// Returns the transition, consuming self in the process.
178    pub fn into_transition(self) -> Transition<N> {
179        self.transition
180    }
181
182    /// Returns the global state root.
183    pub const fn global_state_root(&self) -> N::StateRoot {
184        self.global_state_root
185    }
186
187    /// Returns the proof.
188    pub const fn proof(&self) -> Option<&Proof<N>> {
189        self.proof.as_ref()
190    }
191}
192
193impl<N: Network> Deref for Fee<N> {
194    type Target = Transition<N>;
195
196    fn deref(&self) -> &Self::Target {
197        &self.transition
198    }
199}
200
201#[cfg(test)]
202pub mod test_helpers {
203    use super::*;
204    use algorithms::snark::varuna::VarunaVersion;
205    use console::types::Field;
206    use ledger_query::Query;
207    use ledger_store::{BlockStore, helpers::memory::BlockMemory};
208    use synthesizer_process::Process;
209
210    use aleo_std::StorageMode;
211    use once_cell::sync::OnceCell;
212
213    type CurrentNetwork = console::network::MainnetV0;
214    type CurrentAleo = circuit::network::AleoV0;
215
216    /// Samples a random hardcoded private fee.
217    pub fn sample_fee_private_hardcoded(rng: &mut TestRng) -> Fee<CurrentNetwork> {
218        static INSTANCE: OnceCell<Fee<CurrentNetwork>> = OnceCell::new();
219        INSTANCE
220            .get_or_init(|| {
221                // Sample a deployment or execution ID.
222                let deployment_or_execution_id = Field::rand(rng);
223                // Sample a fee.
224                sample_fee_private(deployment_or_execution_id, rng)
225            })
226            .clone()
227    }
228
229    /// Samples a random private fee.
230    pub fn sample_fee_private(
231        deployment_or_execution_id: Field<CurrentNetwork>,
232        rng: &mut TestRng,
233    ) -> Fee<CurrentNetwork> {
234        // Sample the genesis block, transaction, and private key.
235        let (block, transaction, private_key) = crate::test_helpers::sample_genesis_block_and_components(rng);
236        // Retrieve a credits record.
237        let credits = transaction.records().next().unwrap().1.clone();
238        // Decrypt the record.
239        let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap();
240        // Sample a base fee in microcredits.
241        let base_fee_in_microcredits = 10_000_000;
242        // Sample a priority fee in microcredits.
243        let priority_fee_in_microcredits = 1_000;
244
245        // Initialize the process.
246        let process = Process::load().unwrap();
247        // Authorize the fee.
248        let authorization = process
249            .authorize_fee_private::<CurrentAleo, _>(
250                &private_key,
251                credits,
252                base_fee_in_microcredits,
253                priority_fee_in_microcredits,
254                deployment_or_execution_id,
255                rng,
256            )
257            .unwrap();
258        // Construct the fee trace.
259        let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
260
261        // Initialize a new block store.
262        let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
263        // Insert the block into the block store.
264        // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
265        block_store.insert(&FromStr::from_str(&block.to_string()).unwrap()).unwrap();
266
267        // Prepare the assignments.
268        trace.prepare(Query::from(block_store)).unwrap();
269        // Compute the proof and construct the fee.
270        let fee = trace.prove_fee::<CurrentAleo, _>(VarunaVersion::V1, rng).unwrap();
271
272        // Convert the fee.
273        // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
274        Fee::from_str(&fee.to_string()).unwrap()
275    }
276
277    /// Samples a random hardcoded public fee.
278    pub fn sample_fee_public_hardcoded(rng: &mut TestRng) -> Fee<CurrentNetwork> {
279        static INSTANCE: OnceCell<Fee<CurrentNetwork>> = OnceCell::new();
280        INSTANCE
281            .get_or_init(|| {
282                // Sample a deployment or execution ID.
283                let deployment_or_execution_id = Field::rand(rng);
284                // Sample a fee.
285                sample_fee_public(deployment_or_execution_id, rng)
286            })
287            .clone()
288    }
289
290    /// Samples a random public fee.
291    pub fn sample_fee_public(
292        deployment_or_execution_id: Field<CurrentNetwork>,
293        rng: &mut TestRng,
294    ) -> Fee<CurrentNetwork> {
295        // Sample the genesis block and private key.
296        let (block, _, private_key) = crate::test_helpers::sample_genesis_block_and_components(rng);
297        // Set the base fee amount.
298        let base_fee = 10_000_000;
299        // Set the priority fee amount.
300        let priority_fee = 1_000;
301
302        // Initialize the process.
303        let process = Process::load().unwrap();
304        // Authorize the fee.
305        let authorization = process
306            .authorize_fee_public::<CurrentAleo, _>(
307                &private_key,
308                base_fee,
309                priority_fee,
310                deployment_or_execution_id,
311                rng,
312            )
313            .unwrap();
314        // Construct the fee trace.
315        let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
316
317        // Initialize a new block store.
318        let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
319        // Insert the block into the block store.
320        // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
321        block_store.insert(&FromStr::from_str(&block.to_string()).unwrap()).unwrap();
322
323        // Prepare the assignments.
324        trace.prepare(Query::from(block_store)).unwrap();
325        // Compute the proof and construct the fee.
326        let fee = trace.prove_fee::<CurrentAleo, _>(VarunaVersion::V1, rng).unwrap();
327
328        // Convert the fee.
329        // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules.
330        Fee::from_str(&fee.to_string()).unwrap()
331    }
332}