Skip to main content

snarkvm_console_program/data/dynamic/future/
mod.rs

1// Copyright (c) 2019-2026 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 equal;
18mod parse;
19mod to_bits;
20mod to_fields;
21
22use crate::{Argument, Boolean, Field, Future, Identifier, Network, ProgramID, Result, ToField, ToFields};
23
24use snarkvm_console_algorithms::{Poseidon2, Poseidon8};
25use snarkvm_console_collections::merkle_tree::MerkleTree;
26use snarkvm_console_network::*;
27
28/// The depth of the future argument tree.
29pub const FUTURE_ARGUMENT_TREE_DEPTH: u8 = 4;
30
31/// The future argument tree.
32pub type FutureArgumentTree<E> = MerkleTree<E, Poseidon8<E>, Poseidon2<E>, FUTURE_ARGUMENT_TREE_DEPTH>;
33
34/// A dynamic future is a fixed-size representation of a future. Like static
35/// `Future`s, a dynamic future contains a program name, program network, and function name. These
36/// are however represented as `Field` elements as opposed to `Identifier`s to
37/// ensure a fixed size. Dynamic futures also store a checksum of the
38/// arguments to the future instead of the arguments themselves. This ensures
39/// that all dynamic futures have a constant size, regardless of the amount of
40/// data they contain.
41///
42/// The checksum is computed as `truncate_252(Sha3_256(bits))`, where `bits` is constructed by:
43///   1. Prefixing with the number of arguments as a `u8` in little-endian bits.
44///   2. Appending the type-prefixed `to_bits_le()` of each argument.
45///   3. Padding the result to the next multiple of 8 bits.
46///
47/// The 256-bit SHA-3 output is truncated to the field's data capacity (252 bits) and
48/// packed into a field element.
49#[derive(Clone)]
50pub struct DynamicFuture<N: Network> {
51    /// The program name.
52    program_name: Field<N>,
53    /// The program network.
54    program_network: Field<N>,
55    /// The function name.
56    function_name: Field<N>,
57    /// The checksum of the arguments.
58    checksum: Field<N>,
59    /// The optional arguments.
60    arguments: Option<Vec<Argument<N>>>,
61}
62
63impl<N: Network> DynamicFuture<N> {
64    /// Initializes a dynamic future without checking that the checksum and arguments are consistent.
65    pub fn new_unchecked(
66        program_name: Field<N>,
67        program_network: Field<N>,
68        function_name: Field<N>,
69        checksum: Field<N>,
70        arguments: Option<Vec<Argument<N>>>,
71    ) -> Self {
72        Self { program_name, program_network, function_name, checksum, arguments }
73    }
74}
75
76impl<N: Network> DynamicFuture<N> {
77    /// Returns the program name.
78    pub const fn program_name(&self) -> &Field<N> {
79        &self.program_name
80    }
81
82    /// Returns the program network.
83    pub const fn program_network(&self) -> &Field<N> {
84        &self.program_network
85    }
86
87    /// Returns the function name.
88    pub const fn function_name(&self) -> &Field<N> {
89        &self.function_name
90    }
91
92    /// Returns the checksum of the arguments.
93    pub const fn checksum(&self) -> &Field<N> {
94        &self.checksum
95    }
96
97    /// Returns the optional arguments.
98    pub const fn arguments(&self) -> &Option<Vec<Argument<N>>> {
99        &self.arguments
100    }
101}
102
103impl<N: Network> DynamicFuture<N> {
104    /// Creates a dynamic future from a static future.
105    pub fn from_future(future: &Future<N>) -> Result<Self> {
106        // Get the program name.
107        let program_name = future.program_id().name().to_field()?;
108        // Get the program network.
109        let program_network = future.program_id().network().to_field()?;
110        // Get the function name.
111        let function_name = future.function_name().to_field()?;
112        // Get the arguments.
113        let arguments = future.arguments().to_vec();
114
115        // Get the bits of the arguments.
116        let mut bits = vec![];
117        // Prefix the bits with the number of arguments to ensure that different numbers of arguments produce different hashes.
118        // Note that the number of arguments is at most 16, so it fits in a single byte.
119        u8::try_from(arguments.len())?.write_bits_le(&mut bits);
120        // Then, append the bits of each argument.
121        // Note that the argument bits themselves are type-prefixed.
122        for argument in arguments.iter() {
123            argument.write_bits_le(&mut bits);
124        }
125        // Then pad the bits to the next multiple of 8 to ensure that the hash is consistent regardless of the number of arguments.
126        bits.resize(bits.len().div_ceil(8) * 8, false);
127
128        // Hash the bits of the arguments using SHA-3 256, then truncate to fit in a field element.
129        let hash_bits = N::hash_sha3_256(&bits)?;
130        // Truncate the 256-bit hash to the field's data capacity (252 bits) and pack into a field element.
131        let checksum = Field::<N>::from_bits_le(&hash_bits[..Field::<N>::size_in_data_bits()])?;
132
133        Ok(Self::new_unchecked(program_name, program_network, function_name, checksum, Some(arguments)))
134    }
135
136    /// Creates a static future from a dynamic future.
137    pub fn to_future(&self) -> Result<Future<N>> {
138        // Ensure that the arguments are present.
139        let Some(arguments) = &self.arguments else {
140            bail!("Cannot convert dynamic future to a static future without the arguments being present");
141        };
142
143        Ok(Future::new(
144            ProgramID::try_from((
145                Identifier::from_field(&self.program_name)?,
146                Identifier::from_field(&self.program_network)?,
147            ))?,
148            Identifier::from_field(&self.function_name)?,
149            arguments.clone(),
150        ))
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use snarkvm_console_network::MainnetV0;
158
159    use crate::Plaintext;
160
161    use core::str::FromStr;
162
163    type CurrentNetwork = MainnetV0;
164
165    #[test]
166    fn test_data_depth() {
167        assert_eq!(CurrentNetwork::MAX_INPUTS.ilog2(), FUTURE_ARGUMENT_TREE_DEPTH as u32);
168    }
169
170    fn create_test_future(arguments: Vec<Argument<CurrentNetwork>>) -> Future<CurrentNetwork> {
171        Future::new(ProgramID::from_str("test.aleo").unwrap(), Identifier::from_str("foo").unwrap(), arguments)
172    }
173
174    fn assert_round_trip(arguments: Vec<Argument<CurrentNetwork>>) {
175        let future = create_test_future(arguments);
176        let dynamic = DynamicFuture::from_future(&future).unwrap();
177        let recovered = dynamic.to_future().unwrap();
178        assert_eq!(future.program_id(), recovered.program_id());
179        assert_eq!(future.function_name(), recovered.function_name());
180        assert_eq!(future.arguments().len(), recovered.arguments().len(), "Argument count must be preserved");
181        for (a, b) in future.arguments().iter().zip(recovered.arguments().iter()) {
182            assert!(*a.is_equal(b));
183        }
184    }
185
186    #[test]
187    fn test_round_trip_various_arguments() {
188        // No arguments.
189        assert_round_trip(vec![]);
190
191        // Plaintext literals.
192        assert_round_trip(vec![
193            Argument::Plaintext(Plaintext::from_str("true").unwrap()),
194            Argument::Plaintext(Plaintext::from_str("100u64").unwrap()),
195        ]);
196
197        // Struct and array.
198        assert_round_trip(vec![Argument::Plaintext(Plaintext::from_str("{ x: 1field, y: 2field }").unwrap())]);
199
200        // Nested Future argument.
201        let inner =
202            Future::new(ProgramID::from_str("inner.aleo").unwrap(), Identifier::from_str("bar").unwrap(), vec![
203                Argument::Plaintext(Plaintext::from_str("42u64").unwrap()),
204            ]);
205        assert_round_trip(vec![Argument::Future(inner.clone())]);
206
207        // DynamicFuture argument.
208        assert_round_trip(vec![Argument::DynamicFuture(DynamicFuture::from_future(&inner).unwrap())]);
209
210        // Max arguments (16).
211        let max_args: Vec<_> =
212            (0..16).map(|i| Argument::Plaintext(Plaintext::from_str(&format!("{i}u64")).unwrap())).collect();
213        assert_round_trip(max_args);
214    }
215
216    #[test]
217    fn test_checksum_determinism() {
218        let args1 = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
219        let args2 = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
220        let args3 = vec![Argument::Plaintext(Plaintext::from_str("200u64").unwrap())];
221
222        let d1 = DynamicFuture::from_future(&create_test_future(args1)).unwrap();
223        let d2 = DynamicFuture::from_future(&create_test_future(args2)).unwrap();
224        let d3 = DynamicFuture::from_future(&create_test_future(args3)).unwrap();
225
226        assert_eq!(d1.checksum(), d2.checksum(), "Same arguments should produce same checksum");
227        assert_ne!(d1.checksum(), d3.checksum(), "Different arguments should produce different checksums");
228    }
229
230    #[test]
231    fn test_to_fields_is_deterministic() {
232        let args = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
233        let dynamic = DynamicFuture::from_future(&create_test_future(args)).unwrap();
234
235        // Two calls must return identical field elements.
236        let fields1 = dynamic.to_fields().unwrap();
237        let fields2 = dynamic.to_fields().unwrap();
238        assert!(!fields1.is_empty(), "to_fields must return at least one field element");
239        assert_eq!(fields1, fields2, "to_fields must be deterministic");
240    }
241
242    #[test]
243    fn test_to_fields_differs_for_different_futures() {
244        let args_a = vec![Argument::Plaintext(Plaintext::from_str("1u64").unwrap())];
245        let args_b = vec![Argument::Plaintext(Plaintext::from_str("2u64").unwrap())];
246        let da = DynamicFuture::from_future(&create_test_future(args_a)).unwrap();
247        let db = DynamicFuture::from_future(&create_test_future(args_b)).unwrap();
248
249        // Different futures must produce different field encodings.
250        assert_ne!(da.to_fields().unwrap(), db.to_fields().unwrap());
251    }
252}