whisky_pallas/wrapper/transaction_body/
value.rs

1use pallas::{
2    codec::utils::PositiveCoin,
3    ledger::primitives::{conway::Value as PallasValue, Fragment},
4};
5
6use crate::wrapper::transaction_body::MultiassetPositiveCoin;
7use whisky_common::WError;
8
9#[derive(Debug, PartialEq, Clone)]
10pub struct Value {
11    pub inner: PallasValue,
12}
13
14impl Value {
15    pub fn new(coin: u64, multiasset: Option<MultiassetPositiveCoin>) -> Self {
16        match multiasset {
17            Some(ma) => Self {
18                inner: PallasValue::Multiasset(coin, ma.inner),
19            },
20            None => Self {
21                inner: PallasValue::Coin(coin),
22            },
23        }
24    }
25
26    pub fn add(&self, other: &Value) -> Result<Value, WError> {
27        match (&self.inner, &other.inner) {
28            (PallasValue::Coin(a), PallasValue::Coin(b)) => Ok(Value {
29                inner: PallasValue::Coin(a + b),
30            }),
31            (PallasValue::Coin(a), PallasValue::Multiasset(b, b_ma)) => Ok(Value {
32                inner: PallasValue::Multiasset(a + b, b_ma.clone()),
33            }),
34            (PallasValue::Multiasset(a, a_ma), PallasValue::Coin(b)) => Ok(Value {
35                inner: PallasValue::Multiasset(a + b, a_ma.clone()),
36            }),
37            (PallasValue::Multiasset(a, a_ma), PallasValue::Multiasset(b, b_ma)) => {
38                let mut combined_ma = a_ma.clone();
39                for (policy_id, assets) in b_ma.iter() {
40                    let entry = combined_ma.entry(*policy_id).or_default();
41                    for (asset_name, amount) in assets.iter() {
42                        let asset_entry = entry.entry(asset_name.clone());
43                        match asset_entry {
44                            std::collections::btree_map::Entry::Vacant(_vacant_entry) => {
45                                entry.insert(asset_name.clone(), *amount);
46                            }
47                            std::collections::btree_map::Entry::Occupied(occupied_entry) => {
48                                let new_amount =
49                                    u64::from(*occupied_entry.get()) + u64::from(amount);
50                                *occupied_entry.into_mut() = PositiveCoin::try_from(new_amount)
51                                    .map_err(|_| {
52                                        WError::new(
53                                        "Value - Add:",
54                                        "Failed to create PositiveCoin from added asset amounts",
55                                    )
56                                    })?;
57                            }
58                        }
59                    }
60                }
61                Ok(Value {
62                    inner: PallasValue::Multiasset(a + b, combined_ma),
63                })
64            }
65        }
66    }
67
68    pub fn sub(&self, other: &Value) -> Result<Value, WError> {
69        match (&self.inner, &other.inner) {
70            (PallasValue::Coin(a), PallasValue::Coin(b)) => Ok(Value {
71                inner: PallasValue::Coin(a - b),
72            }),
73            (PallasValue::Coin(a), PallasValue::Multiasset(b, b_ma)) => Ok(Value {
74                inner: PallasValue::Multiasset(a - b, b_ma.clone()),
75            }),
76            (PallasValue::Multiasset(a, a_ma), PallasValue::Coin(b)) => Ok(Value {
77                inner: PallasValue::Multiasset(a - b, a_ma.clone()),
78            }),
79            (PallasValue::Multiasset(a, a_ma), PallasValue::Multiasset(b, b_ma)) => {
80                let mut combined_ma = a_ma.clone();
81                for (policy_id, assets) in b_ma.iter() {
82                    if let Some(entry) = combined_ma.get_mut(policy_id) {
83                        for (asset_name, amount) in assets.iter() {
84                            if let Some(asset_entry) = entry.get_mut(asset_name) {
85                                let new_amount = u64::from(*asset_entry) - u64::from(amount);
86                                if new_amount == 0 {
87                                    entry.remove(asset_name);
88                                } else {
89                                    *asset_entry = PositiveCoin::try_from(new_amount).map_err(|_| {
90                                    WError::new(
91                                        "Value - Sub:",
92                                        "Failed to create PositiveCoin from subtracted asset amounts",
93                                    )
94                                })?;
95                                }
96                            }
97                        }
98                    }
99                }
100                for (policy_id, _assets) in b_ma.iter() {
101                    if let Some(entry) = combined_ma.get_mut(policy_id) {
102                        if entry.is_empty() {
103                            combined_ma.remove(policy_id);
104                        }
105                    }
106                }
107                Ok(Value {
108                    inner: PallasValue::Multiasset(a - b, combined_ma),
109                })
110            }
111        }
112    }
113
114    pub fn encode(&self) -> Result<String, WError> {
115        let bytes = self.inner.encode_fragment().map_err(|e| {
116            WError::new(
117                "Value - Encode:",
118                &format!("Fragment encoding failed: {}", e),
119            )
120        })?;
121        Ok(hex::encode(bytes))
122    }
123
124    pub fn decode_bytes(bytes: &[u8]) -> Result<Self, WError> {
125        let inner = PallasValue::decode_fragment(bytes).map_err(|e| {
126            WError::new("Value - Decode:", &format!("Fragment decode error: {}", e))
127        })?;
128        Ok(Self { inner })
129    }
130}