aleo_rust/program/helpers/
records.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the Aleo SDK library.
3
4// The Aleo SDK library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Aleo SDK library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use snarkvm_console::account::Group;
19
20/// Helper struct for finding records on chain during program development
21#[derive(Clone)]
22pub struct RecordFinder<N: Network> {
23    api_client: AleoAPIClient<N>,
24}
25
26impl<N: Network> RecordFinder<N> {
27    pub fn new(api_client: AleoAPIClient<N>) -> Self {
28        Self { api_client }
29    }
30
31    /// Resolve two records for a transfer amount and fee respectively
32    ///
33    /// Basic Usage:
34    /// let (amount_record, fee_record) = self.resolve_amount_and_fee(amount, fee, private_key);
35    #[allow(clippy::type_complexity)]
36    pub fn find_amount_and_fee_records(
37        &self,
38        amount: u64,
39        fee: u64,
40        private_key: &PrivateKey<N>,
41    ) -> Result<(Record<N, Plaintext<N>>, Record<N, Plaintext<N>>)> {
42        let records = self.find_record_amounts(vec![amount, fee], private_key)?;
43        if records.len() < 2 { bail!("Insufficient funds") } else { Ok((records[0].clone(), records[1].clone())) }
44    }
45
46    /// Resolve a record with a specific value. If successful it will return a record with a gate
47    /// value equal to or greater than the specified amount.
48    pub fn find_one_record(
49        &self,
50        private_key: &PrivateKey<N>,
51        amount: u64,
52        found_nonces: Option<&[Group<N>]>,
53    ) -> Result<Record<N, Plaintext<N>>> {
54        let step_size = 49u32;
55        let amounts = Some(vec![amount]);
56        let current_height = self.api_client.latest_height()?;
57        let mut end_height = current_height;
58        let mut start_height = end_height.saturating_sub(49);
59        for _ in (0..current_height).step_by(step_size as usize) {
60            let result = self
61                .api_client
62                .get_unspent_records(private_key, start_height..end_height, None, amounts.as_ref())
63                .map_or(vec![], |records| records)
64                .into_iter()
65                .find(|(_, record)| {
66                    record.microcredits().unwrap_or(0) >= amount && {
67                        if let Some(found_nonces) = found_nonces {
68                            !found_nonces.contains(record.nonce())
69                        } else {
70                            true
71                        }
72                    }
73                })
74                .ok_or_else(|| anyhow!("Insufficient funds"));
75
76            if let Ok(record) = result {
77                return Ok(record.1);
78            }
79            end_height = start_height;
80            start_height = start_height.saturating_sub(step_size);
81        }
82        bail!("Insufficient funds")
83    }
84
85    /// Attempt to resolve records with specific gate values specified as a vector of u64s. If the
86    /// function is successful at resolving the records, it will return a vector of records with
87    /// microcredits equal to or greater than the specified amounts. If it cannot resolve records
88    /// with the specified amounts, it will return an error.
89    pub fn find_record_amounts(
90        &self,
91        amounts: Vec<u64>,
92        private_key: &PrivateKey<N>,
93    ) -> Result<Vec<Record<N, Plaintext<N>>>> {
94        self.find_unspent_records_on_chain(Some(&amounts), None, private_key)
95    }
96
97    pub fn find_unspent_records_on_chain(
98        &self,
99        amounts: Option<&Vec<u64>>,
100        max_microcredits: Option<u64>,
101        private_key: &PrivateKey<N>,
102    ) -> Result<Vec<Record<N, Plaintext<N>>>> {
103        let latest_height = self.api_client.latest_height()?;
104        let records = self.api_client.get_unspent_records(private_key, 0..latest_height, max_microcredits, amounts)?;
105        Ok(records.into_iter().map(|(_, record)| record).collect())
106    }
107
108    /// Find matching records from a program using a user-specified function to match records
109    pub fn find_matching_records_from_program(
110        &self,
111        private_key: &PrivateKey<N>,
112        program_id: &ProgramID<N>,
113        matching_function: impl FnOnce(Vec<Record<N, Plaintext<N>>>) -> Result<Vec<Record<N, Plaintext<N>>>>,
114        unspent_only: bool,
115        max_records: Option<usize>,
116    ) -> Result<Vec<Record<N, Plaintext<N>>>> {
117        let latest_height = self.api_client.latest_height()?;
118        let view_key = ViewKey::try_from(private_key)?;
119        let records = self.api_client.get_program_records(
120            private_key,
121            program_id,
122            0..latest_height,
123            unspent_only,
124            max_records,
125        )?;
126        let decrypted_records =
127            records.into_iter().map(|(_, record)| record.decrypt(&view_key).unwrap()).collect::<Vec<_>>();
128        matching_function(decrypted_records)
129    }
130}