Skip to main content

whisky_common/data/
value.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    data::{ByteString, Int, Map, PlutusDataJson},
5    models::Asset,
6    WError,
7};
8use std::collections::BTreeMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct Value(pub BTreeMap<String, u64>);
12
13impl Value {
14    pub fn new() -> Self {
15        Value(BTreeMap::new())
16    }
17
18    pub fn from_asset(asset: &Asset) -> Self {
19        let mut asset_map = BTreeMap::new();
20        asset_map.insert(
21            Value::sanitize_unit(&asset.unit()),
22            asset.quantity().parse::<u64>().unwrap(),
23        );
24        Value(asset_map)
25    }
26
27    pub fn from_asset_vec(assets: &[Asset]) -> Self {
28        let mut asset_map = BTreeMap::new();
29        for asset in assets {
30            let current_value = asset_map
31                .entry(Value::sanitize_unit(&asset.unit()))
32                .or_insert(0);
33            *current_value += asset.quantity().parse::<u64>().unwrap();
34        }
35        Value(asset_map)
36    }
37
38    pub fn add_asset(&mut self, unit: &str, quantity: u64) -> &mut Self {
39        let current_value = self.0.entry(Value::sanitize_unit(unit)).or_insert(0);
40        *current_value += quantity;
41        self
42    }
43
44    pub fn add_assets(&mut self, assets: &[Asset]) -> &mut Self {
45        for asset in assets {
46            self.add_asset(&asset.unit(), asset.quantity().parse::<u64>().unwrap());
47        }
48        self
49    }
50
51    pub fn merge(&mut self, other: &Value) -> &mut Self {
52        for (key, value) in other.0.clone() {
53            let current_value = self.0.entry(Value::sanitize_unit(&key)).or_insert(0);
54            *current_value += value;
55        }
56        self
57    }
58
59    pub fn negate_asset(&mut self, unit: &str, quantity: u64) -> &mut Self {
60        let current_value = self.0.entry(Value::sanitize_unit(unit)).or_insert(0);
61        if *current_value <= quantity {
62            self.0.remove(unit);
63        } else {
64            *current_value -= quantity;
65        };
66        self
67    }
68
69    pub fn negate_assets(&mut self, other: &[Asset]) -> &mut Self {
70        for asset in other {
71            self.negate_asset(&asset.unit(), asset.quantity().parse::<u64>().unwrap());
72        }
73        self
74    }
75
76    pub fn negate_value(&mut self, other: &Value) -> &mut Self {
77        for (key, value) in other.0.clone() {
78            let unit = Value::sanitize_unit(&key);
79            let current_value = self.0.entry(unit.clone()).or_insert(0);
80            if *current_value <= value {
81                self.0.remove(&unit);
82            } else {
83                *current_value -= value;
84            }
85        }
86        self
87    }
88
89    pub fn to_asset_vec(&self) -> Vec<Asset> {
90        let mut assets = vec![];
91        for (unit, quantity) in &self.0 {
92            assets.push(Asset::new(Value::sanitize_unit(unit), quantity.to_string()));
93        }
94        assets
95    }
96
97    // Accessor
98    pub fn get(&self, key: &str) -> u64 {
99        let key = if key.is_empty() { "lovelace" } else { key };
100        match self.0.get(key) {
101            Some(value) => *value,
102            None => 0,
103        }
104    }
105
106    pub fn get_policy_assets(&self, policy_id: &str) -> Vec<Asset> {
107        let mut assets = vec![];
108        for (unit, quantity) in &self.0 {
109            if unit.starts_with(policy_id) {
110                assets.push(Asset::new(unit.clone(), quantity.to_string()));
111            }
112        }
113        assets
114    }
115
116    pub fn keys(&self) -> Vec<String> {
117        self.0.keys().cloned().collect()
118    }
119
120    pub fn len(&self) -> usize {
121        self.0.len()
122    }
123
124    // Comparison function
125    pub fn geq(&self, other: &Value) -> bool {
126        for (key, value) in &other.0 {
127            if self
128                .0
129                .get(&Value::sanitize_unit(key))
130                .is_some_and(|v| v < value)
131            {
132                return false;
133            }
134        }
135        true
136    }
137
138    pub fn leq(&self, other: &Value) -> bool {
139        for (key, value) in &other.0 {
140            if self
141                .0
142                .get(&Value::sanitize_unit(key))
143                .is_some_and(|v| v > value)
144            {
145                return false;
146            }
147        }
148        true
149    }
150
151    pub fn is_empty(&self) -> bool {
152        self.0.is_empty()
153    }
154
155    pub fn sanitize_unit(unit: &str) -> String {
156        if unit.is_empty() {
157            "lovelace".to_string()
158        } else {
159            unit.to_string()
160        }
161    }
162}
163
164impl PlutusDataJson for Value {
165    fn to_json(&self) -> serde_json::Value {
166        let mut value_map: BTreeMap<String, BTreeMap<String, u64>> = BTreeMap::new();
167
168        self.0.iter().for_each(|(unit, quantity)| {
169            let sanitized_name = unit.replace("lovelace", "");
170            let policy = &sanitized_name[..56.min(sanitized_name.len())];
171            let token = &sanitized_name[56.min(sanitized_name.len())..];
172
173            value_map
174                .entry(policy.to_string())
175                .or_insert_with(BTreeMap::new)
176                .entry(token.to_string())
177                .and_modify(|q| *q += quantity)
178                .or_insert(*quantity);
179        });
180
181        let json_map = value_map
182            .into_iter() // Keys will already be sorted
183            .map(|(policy, tokens)| {
184                (
185                    ByteString::new(&policy),
186                    tokens
187                        .into_iter() // Token keys will already be sorted
188                        .map(|(token, quantity)| {
189                            (ByteString::new(&token), Int::new(quantity as i128))
190                        })
191                        .collect(),
192                )
193            })
194            .collect::<Map<ByteString, Map<ByteString, Int>>>();
195        json_map.to_json()
196    }
197
198    fn from_json(value: &serde_json::Value) -> Result<Self, WError> {
199        // Parse as Map<ByteString, Map<ByteString, Int>>
200        let outer_map = Map::<ByteString, Map<ByteString, Int>>::from_json(value)
201            .map_err(WError::add_err_trace("Value::from_json"))?;
202
203        let mut asset_map = BTreeMap::new();
204
205        for (policy_bytes, tokens_map) in outer_map.map {
206            let policy = policy_bytes.bytes;
207
208            for (token_bytes, quantity) in tokens_map.map {
209                let token = token_bytes.bytes;
210                let unit = if policy.is_empty() {
211                    "lovelace".to_string()
212                } else {
213                    format!("{}{}", policy, token)
214                };
215
216                // quantity.int is i128, convert to u64
217                let qty = quantity.int as u64;
218                asset_map
219                    .entry(unit)
220                    .and_modify(|q| *q += qty)
221                    .or_insert(qty);
222            }
223        }
224
225        Ok(Value(asset_map))
226    }
227}
228
229impl Default for Value {
230    fn default() -> Self {
231        Value::new()
232    }
233}