whisky_common/data/
value.rs1use 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 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 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() .map(|(policy, tokens)| {
184 (
185 ByteString::new(&policy),
186 tokens
187 .into_iter() .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 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 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}