1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Aleo SDK library.

// The Aleo SDK library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The Aleo SDK library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>.

use super::*;

/// Helper struct for finding records on chain during program development
#[derive(Clone)]
pub struct RecordFinder<N: Network> {
    api_client: AleoAPIClient<N>,
}

impl<N: Network> RecordFinder<N> {
    pub fn new(api_client: AleoAPIClient<N>) -> Self {
        Self { api_client }
    }

    /// Resolve two records for a transfer amount and fee respectively
    ///
    /// Basic Usage:
    /// let (amount_record, fee_record) = self.resolve_amount_and_fee(amount, fee, private_key);
    #[allow(clippy::type_complexity)]
    pub fn find_amount_and_fee_records(
        &self,
        amount: u64,
        fee: u64,
        private_key: &PrivateKey<N>,
    ) -> Result<(Record<N, Plaintext<N>>, Record<N, Plaintext<N>>)> {
        let records = self.find_record_amounts(vec![amount, fee], private_key)?;
        if records.len() < 2 { bail!("Insufficient funds") } else { Ok((records[0].clone(), records[1].clone())) }
    }

    /// Resolve a record with a specific value. If successful it will return a record with a gate
    /// value equal to or greater than the specified amount.
    pub fn find_one_record(&self, private_key: &PrivateKey<N>, amount: u64) -> Result<Record<N, Plaintext<N>>> {
        let amounts = vec![amount];
        self.find_unspent_records_on_chain(Some(&amounts), None, private_key)?
            .into_iter()
            .find(|record| record.microcredits().unwrap_or(0) >= amount)
            .ok_or_else(|| anyhow!("Insufficient funds"))
    }

    /// Attempt to resolve records with specific gate values specified as a vector of u64s. If the
    /// function is successful at resolving the records, it will return a vector of records with
    /// microcredits equal to or greater than the specified amounts. If it cannot resolve records
    /// with the specified amounts, it will return an error.
    pub fn find_record_amounts(
        &self,
        amounts: Vec<u64>,
        private_key: &PrivateKey<N>,
    ) -> Result<Vec<Record<N, Plaintext<N>>>> {
        self.find_unspent_records_on_chain(Some(&amounts), None, private_key)
    }

    pub fn find_unspent_records_on_chain(
        &self,
        amounts: Option<&Vec<u64>>,
        max_microcredits: Option<u64>,
        private_key: &PrivateKey<N>,
    ) -> Result<Vec<Record<N, Plaintext<N>>>> {
        let latest_height = self.api_client.latest_height()?;
        let records = self.api_client.get_unspent_records(private_key, 0..latest_height, max_microcredits, amounts)?;
        Ok(records.into_iter().map(|(_, record)| record).collect())
    }
}