sidan_csl_rs/core/algo/
utxo_selection.rs

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use cardano_serialization_lib::JsError;

use crate::{model::*, *};
use std::collections::HashSet;

#[wasm_bindgen]
pub fn js_select_utxos(
    json_str_inputs: &str,
    json_str_required_assets: &str,
    threshold: &str,
) -> Result<String, JsError> {
    let inputs: Vec<UTxO> =
        serde_json::from_str(json_str_inputs).expect("Error deserializing inputs");
    let required_assets: Vec<Asset> = serde_json::from_str(json_str_required_assets)
        .expect("Error deserializing required_assets");
    let required_value = Value::from_asset_vec(&required_assets);

    select_utxos(&inputs, required_value, threshold)
        .map_err(|e| JsError::from_str(&e))
        .map(|utxos| serde_json::to_string(&utxos).unwrap())
}

pub fn select_utxos(
    inputs: &[UTxO],
    required_assets: Value,
    threshold: &str,
) -> Result<Vec<UTxO>, String> {
    let mut total_required_assets = required_assets.clone();
    total_required_assets.add_asset("lovelace", threshold.parse::<u64>().unwrap());

    // Classify the utxos
    let mut only_lovelace: Vec<usize> = vec![];
    let mut singleton: Vec<usize> = vec![];
    let mut pair: Vec<usize> = vec![];
    let mut rest: Vec<usize> = vec![];
    for (index, utxo) in inputs.iter().enumerate() {
        match utxo.output.amount.len() {
            1 => only_lovelace.push(index),
            2 => singleton.push(index),
            3 => pair.push(index),
            _ => rest.push(index),
        }
    }

    let mut used_utxos: HashSet<usize> = HashSet::new();

    let mut use_utxo = |index: usize, total_required_assets: &mut Value| {
        let utxo = inputs[index].clone();
        for asset in utxo.output.amount {
            total_required_assets
                .negate_asset(&asset.unit(), asset.quantity().parse::<u64>().unwrap());
            used_utxos.insert(index);
        }
    };

    let mut process_list = |list: Vec<usize>, unit: String, total_required_assets: &mut Value| {
        for index in list {
            let required_asset_value = total_required_assets.get(&unit);
            if required_asset_value == 0 {
                return;
            }
            let utxo = inputs[index].clone();
            for asset in utxo.output.amount {
                if asset.unit() == unit {
                    use_utxo(index, total_required_assets);
                    break;
                }
            }
        }
    };

    let required_units: Vec<String> = total_required_assets.keys();

    for unit in required_units.clone() {
        if unit != *"lovelace" && total_required_assets.get(&unit) > 0 {
            process_list(singleton.clone(), unit.clone(), &mut total_required_assets);
            process_list(pair.clone(), unit.clone(), &mut total_required_assets);
            process_list(rest.clone(), unit.clone(), &mut total_required_assets);
        }
    }

    process_list(
        only_lovelace.clone(),
        "lovelace".to_string(),
        &mut total_required_assets,
    );

    process_list(
        singleton.clone(),
        "lovelace".to_string(),
        &mut total_required_assets,
    );
    process_list(
        pair.clone(),
        "lovelace".to_string(),
        &mut total_required_assets,
    );
    process_list(
        rest.clone(),
        "lovelace".to_string(),
        &mut total_required_assets,
    );

    for unit in required_units.clone() {
        if total_required_assets.get(&unit) > 0 {
            println!("Total required assets: {:?}", total_required_assets);
            return Err(format!(
                "Selection failed, {:?} value missing. Reminder, in this selection you required for {:?} extra lovelace as threshold",
                total_required_assets,
                threshold
            ));
        }
    }

    let mut selected_utxos: Vec<UTxO> = vec![];
    for index in used_utxos.iter() {
        selected_utxos.push(inputs[*index].clone());
    }

    Ok(selected_utxos)
}

#[test]
fn test_basic_selection() {
    let utxo_list = vec![UTxO {
        input: UtxoInput {
            output_index: 0,
            tx_hash: "test".to_string(),
        },
        output: UtxoOutput {
            address: "test".to_string(),
            amount: vec![Asset::new_from_str("lovelace", "10000000")],
            data_hash: None,
            plutus_data: None,
            script_ref: None,
            script_hash: None,
        },
    }];

    let mut required_assets: Value = Value::new();
    required_assets.add_asset("lovelace", 5_000_000);
    let selected_list = select_utxos(&utxo_list, required_assets, "5000000").unwrap();
    assert_eq!(utxo_list, selected_list);
}