mod bytes;
mod equal;
mod parse;
mod to_bits;
mod to_fields;
use crate::{Argument, Boolean, Field, Future, Identifier, Network, ProgramID, Result, ToField, ToFields};
use snarkvm_console_algorithms::{Poseidon2, Poseidon8};
use snarkvm_console_collections::merkle_tree::MerkleTree;
use snarkvm_console_network::*;
pub const FUTURE_ARGUMENT_TREE_DEPTH: u8 = 4;
pub type FutureArgumentTree<E> = MerkleTree<E, Poseidon8<E>, Poseidon2<E>, FUTURE_ARGUMENT_TREE_DEPTH>;
#[derive(Clone)]
pub struct DynamicFuture<N: Network> {
program_name: Field<N>,
program_network: Field<N>,
function_name: Field<N>,
checksum: Field<N>,
arguments: Option<Vec<Argument<N>>>,
}
impl<N: Network> DynamicFuture<N> {
pub fn new_unchecked(
program_name: Field<N>,
program_network: Field<N>,
function_name: Field<N>,
checksum: Field<N>,
arguments: Option<Vec<Argument<N>>>,
) -> Self {
Self { program_name, program_network, function_name, checksum, arguments }
}
}
impl<N: Network> DynamicFuture<N> {
pub const fn program_name(&self) -> &Field<N> {
&self.program_name
}
pub const fn program_network(&self) -> &Field<N> {
&self.program_network
}
pub const fn function_name(&self) -> &Field<N> {
&self.function_name
}
pub const fn checksum(&self) -> &Field<N> {
&self.checksum
}
pub const fn arguments(&self) -> &Option<Vec<Argument<N>>> {
&self.arguments
}
}
impl<N: Network> DynamicFuture<N> {
pub fn from_future(future: &Future<N>) -> Result<Self> {
let program_name = future.program_id().name().to_field()?;
let program_network = future.program_id().network().to_field()?;
let function_name = future.function_name().to_field()?;
let arguments = future.arguments().to_vec();
let mut bits = vec![];
u8::try_from(arguments.len())?.write_bits_le(&mut bits);
for argument in arguments.iter() {
argument.write_bits_le(&mut bits);
}
bits.resize(bits.len().div_ceil(8) * 8, false);
let hash_bits = N::hash_sha3_256(&bits)?;
let checksum = Field::<N>::from_bits_le(&hash_bits[..Field::<N>::size_in_data_bits()])?;
Ok(Self::new_unchecked(program_name, program_network, function_name, checksum, Some(arguments)))
}
pub fn to_future(&self) -> Result<Future<N>> {
let Some(arguments) = &self.arguments else {
bail!("Cannot convert dynamic future to a static future without the arguments being present");
};
Ok(Future::new(
ProgramID::try_from((
Identifier::from_field(&self.program_name)?,
Identifier::from_field(&self.program_network)?,
))?,
Identifier::from_field(&self.function_name)?,
arguments.clone(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use snarkvm_console_network::MainnetV0;
use crate::Plaintext;
use core::str::FromStr;
type CurrentNetwork = MainnetV0;
#[test]
fn test_data_depth() {
assert_eq!(CurrentNetwork::MAX_INPUTS.ilog2(), FUTURE_ARGUMENT_TREE_DEPTH as u32);
}
fn create_test_future(arguments: Vec<Argument<CurrentNetwork>>) -> Future<CurrentNetwork> {
Future::new(ProgramID::from_str("test.aleo").unwrap(), Identifier::from_str("foo").unwrap(), arguments)
}
fn assert_round_trip(arguments: Vec<Argument<CurrentNetwork>>) {
let future = create_test_future(arguments);
let dynamic = DynamicFuture::from_future(&future).unwrap();
let recovered = dynamic.to_future().unwrap();
assert_eq!(future.program_id(), recovered.program_id());
assert_eq!(future.function_name(), recovered.function_name());
assert_eq!(future.arguments().len(), recovered.arguments().len(), "Argument count must be preserved");
for (a, b) in future.arguments().iter().zip(recovered.arguments().iter()) {
assert!(*a.is_equal(b));
}
}
#[test]
fn test_round_trip_various_arguments() {
assert_round_trip(vec![]);
assert_round_trip(vec![
Argument::Plaintext(Plaintext::from_str("true").unwrap()),
Argument::Plaintext(Plaintext::from_str("100u64").unwrap()),
]);
assert_round_trip(vec![Argument::Plaintext(Plaintext::from_str("{ x: 1field, y: 2field }").unwrap())]);
let inner =
Future::new(ProgramID::from_str("inner.aleo").unwrap(), Identifier::from_str("bar").unwrap(), vec![
Argument::Plaintext(Plaintext::from_str("42u64").unwrap()),
]);
assert_round_trip(vec![Argument::Future(inner.clone())]);
assert_round_trip(vec![Argument::DynamicFuture(DynamicFuture::from_future(&inner).unwrap())]);
let max_args: Vec<_> =
(0..16).map(|i| Argument::Plaintext(Plaintext::from_str(&format!("{i}u64")).unwrap())).collect();
assert_round_trip(max_args);
}
#[test]
fn test_checksum_determinism() {
let args1 = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
let args2 = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
let args3 = vec![Argument::Plaintext(Plaintext::from_str("200u64").unwrap())];
let d1 = DynamicFuture::from_future(&create_test_future(args1)).unwrap();
let d2 = DynamicFuture::from_future(&create_test_future(args2)).unwrap();
let d3 = DynamicFuture::from_future(&create_test_future(args3)).unwrap();
assert_eq!(d1.checksum(), d2.checksum(), "Same arguments should produce same checksum");
assert_ne!(d1.checksum(), d3.checksum(), "Different arguments should produce different checksums");
}
#[test]
fn test_to_fields_is_deterministic() {
let args = vec![Argument::Plaintext(Plaintext::from_str("100u64").unwrap())];
let dynamic = DynamicFuture::from_future(&create_test_future(args)).unwrap();
let fields1 = dynamic.to_fields().unwrap();
let fields2 = dynamic.to_fields().unwrap();
assert!(!fields1.is_empty(), "to_fields must return at least one field element");
assert_eq!(fields1, fields2, "to_fields must be deterministic");
}
#[test]
fn test_to_fields_differs_for_different_futures() {
let args_a = vec![Argument::Plaintext(Plaintext::from_str("1u64").unwrap())];
let args_b = vec![Argument::Plaintext(Plaintext::from_str("2u64").unwrap())];
let da = DynamicFuture::from_future(&create_test_future(args_a)).unwrap();
let db = DynamicFuture::from_future(&create_test_future(args_b)).unwrap();
assert_ne!(da.to_fields().unwrap(), db.to_fields().unwrap());
}
}