waves_rust/model/transaction/
issue_transaction.rs

1use crate::error::{Error, Result};
2use crate::model::{Amount, AssetId, Base64String, ByteString};
3use crate::util::JsonDeserializer;
4use crate::waves_proto::IssueTransactionData;
5use serde_json::{Map, Value};
6
7const TYPE: u8 = 3;
8
9#[derive(Clone, Eq, PartialEq, Debug)]
10pub struct IssueTransactionInfo {
11    asset_id: AssetId,
12    name: String,
13    description: String,
14    quantity: u64,
15    decimals: u32,
16    is_reissuable: bool,
17    script: Option<Base64String>,
18}
19
20impl IssueTransactionInfo {
21    pub fn new(
22        asset_id: AssetId,
23        name: String,
24        description: String,
25        quantity: u64,
26        decimals: u32,
27        is_reissuable: bool,
28        script: Option<Base64String>,
29    ) -> Self {
30        IssueTransactionInfo {
31            asset_id,
32            name,
33            description,
34            quantity,
35            decimals,
36            is_reissuable,
37            script,
38        }
39    }
40
41    pub fn tx_type() -> u8 {
42        TYPE
43    }
44
45    pub fn asset_id(&self) -> AssetId {
46        self.asset_id.clone()
47    }
48
49    pub fn name(&self) -> String {
50        self.name.clone()
51    }
52
53    pub fn description(&self) -> String {
54        self.description.clone()
55    }
56
57    pub fn quantity(&self) -> u64 {
58        self.quantity
59    }
60
61    pub fn decimals(&self) -> u32 {
62        self.decimals
63    }
64
65    pub fn is_reissuable(&self) -> bool {
66        self.is_reissuable
67    }
68
69    pub fn script(&self) -> Option<Base64String> {
70        self.script.clone()
71    }
72}
73
74impl TryFrom<&Value> for IssueTransactionInfo {
75    type Error = Error;
76
77    fn try_from(value: &Value) -> Result<Self> {
78        let issue_transaction: IssueTransaction = value.try_into()?;
79        let asset_id = AssetId::from_string(&JsonDeserializer::safe_to_string_from_field(
80            value, "assetId",
81        )?)?;
82
83        Ok(IssueTransactionInfo {
84            asset_id,
85            name: issue_transaction.name(),
86            description: issue_transaction.description(),
87            quantity: issue_transaction.quantity(),
88            decimals: issue_transaction.decimals(),
89            is_reissuable: issue_transaction.is_reissuable(),
90            script: issue_transaction.script(),
91        })
92    }
93}
94
95#[derive(Clone, Eq, PartialEq, Debug)]
96pub struct IssueTransaction {
97    name: String,
98    description: String,
99    quantity: u64,
100    decimals: u32,
101    is_reissuable: bool,
102    script: Option<Base64String>,
103}
104
105impl IssueTransaction {
106    pub fn new(
107        name: String,
108        description: String,
109        quantity: u64,
110        decimals: u32,
111        is_reissuable: bool,
112        script: Option<Base64String>,
113    ) -> Self {
114        IssueTransaction {
115            name,
116            description,
117            quantity,
118            decimals,
119            is_reissuable,
120            script,
121        }
122    }
123
124    pub fn tx_type() -> u8 {
125        TYPE
126    }
127
128    pub fn name(&self) -> String {
129        self.name.clone()
130    }
131
132    pub fn description(&self) -> String {
133        self.description.clone()
134    }
135
136    pub fn quantity(&self) -> u64 {
137        self.quantity
138    }
139
140    pub fn decimals(&self) -> u32 {
141        self.decimals
142    }
143
144    pub fn is_reissuable(&self) -> bool {
145        self.is_reissuable
146    }
147
148    pub fn script(&self) -> Option<Base64String> {
149        self.script.clone()
150    }
151
152    pub fn min_fee(&self) -> Amount {
153        let value = if self.is_nft_issue() {
154            100_000
155        } else {
156            100_000_000
157        };
158        Amount::new(value, None)
159    }
160
161    pub fn is_nft_issue(&self) -> bool {
162        self.quantity == 1 && self.decimals == 0 && !self.is_reissuable
163    }
164}
165
166impl TryFrom<&IssueTransaction> for IssueTransactionData {
167    type Error = Error;
168
169    fn try_from(issue_tx: &IssueTransaction) -> Result<Self> {
170        let script = match issue_tx.script() {
171            Some(script) => script.bytes(),
172            None => vec![],
173        };
174
175        Ok(IssueTransactionData {
176            name: issue_tx.name(),
177            description: issue_tx.description(),
178            amount: issue_tx.quantity() as i64,
179            decimals: issue_tx.decimals() as i32,
180            reissuable: issue_tx.is_reissuable(),
181            script,
182        })
183    }
184}
185
186impl TryFrom<&Value> for IssueTransaction {
187    type Error = Error;
188
189    fn try_from(value: &Value) -> Result<Self> {
190        let name = JsonDeserializer::safe_to_string_from_field(value, "name")?;
191        let description = JsonDeserializer::safe_to_string_from_field(value, "description")?;
192        let quantity = JsonDeserializer::safe_to_int_from_field(value, "quantity")? as u64;
193        let decimals = JsonDeserializer::safe_to_int_from_field(value, "decimals")? as u32;
194        let is_reissuable = JsonDeserializer::safe_to_boolean_from_field(value, "reissuable")?;
195        let script = match value["script"].as_str() {
196            Some(val) => Some(Base64String::from_string(val)?),
197            None => None,
198        };
199
200        Ok(IssueTransaction {
201            name,
202            description,
203            quantity,
204            decimals,
205            is_reissuable,
206            script,
207        })
208    }
209}
210
211impl TryFrom<&IssueTransaction> for Map<String, Value> {
212    type Error = Error;
213
214    fn try_from(issue_tx: &IssueTransaction) -> Result<Self> {
215        let mut json = Map::new();
216        json.insert("name".to_string(), issue_tx.name().into());
217        json.insert("description".to_string(), issue_tx.description().into());
218        json.insert("quantity".to_string(), issue_tx.quantity().into());
219        json.insert("decimals".to_string(), issue_tx.decimals().into());
220        json.insert("reissuable".to_string(), issue_tx.is_reissuable().into());
221        json.insert(
222            "script".to_string(),
223            issue_tx.script().map(|it| it.encoded()).into(),
224        );
225        Ok(json)
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use crate::error::Result;
232    use crate::model::{Base64String, ByteString, IssueTransaction, IssueTransactionInfo};
233    use crate::waves_proto::IssueTransactionData;
234    use serde_json::{json, Map, Value};
235    use std::borrow::Borrow;
236    use std::fs;
237
238    #[test]
239    fn test_json_to_issue_transaction() {
240        let data = fs::read_to_string("./tests/resources/issue_transaction_rs.json")
241            .expect("Unable to read file");
242        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
243
244        let issue_tx_from_json: IssueTransactionInfo = json.borrow().try_into().unwrap();
245
246        assert_eq!(
247            "5HCFX88m6Xxws4SunQuW9ghvYBmk8rK8b6xVCRL8PyAw",
248            issue_tx_from_json.asset_id().encoded()
249        );
250        assert_eq!("test asset", issue_tx_from_json.name());
251        assert_eq!(32, issue_tx_from_json.quantity());
252        assert_eq!(false, issue_tx_from_json.is_reissuable());
253        assert_eq!(3, issue_tx_from_json.decimals());
254        assert_eq!("this is test asset", issue_tx_from_json.description());
255
256        let script = "base64:AgQAAAAHbWFzdGVyMQkBAAAAEWFkZHJlc3NGcm9tU3RyaW5nAAAAAQIAAAAQMzMzbWFzdGVyQWRkcmVzcwQAAAAHJG1hdGNoMAUAAAACdHgDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAE1RyYW5zZmVyVHJhbnNhY3Rpb24EAAAAAXQFAAAAByRtYXRjaDADCQAAAAAAAAIIBQAAAAF0AAAABnNlbmRlcgUAAAAHbWFzdGVyMQYJAAAAAAAAAggFAAAAAXQAAAAJcmVjaXBpZW50BQAAAAdtYXN0ZXIxAwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAABdNYXNzVHJhbnNmZXJUcmFuc2FjdGlvbgQAAAACbXQFAAAAByRtYXRjaDAJAAAAAAAAAggFAAAAAm10AAAABnNlbmRlcgUAAAAHbWFzdGVyMQMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATRXhjaGFuZ2VUcmFuc2FjdGlvbgcGFLbwIw==";
257
258        assert_eq!(
259            script,
260            issue_tx_from_json.script().unwrap().encoded_with_prefix()
261        );
262    }
263
264    #[test]
265    fn test_issue_transaction_to_proto() -> Result<()> {
266        let issue_tx = &IssueTransaction::new(
267            "name".to_owned(),
268            "descr".to_owned(),
269            32,
270            0,
271            false,
272            Some(Base64String::from_bytes(vec![1, 2, 3])),
273        );
274        let proto: IssueTransactionData = issue_tx.try_into()?;
275        assert_eq!(proto.name, issue_tx.name());
276        assert_eq!(proto.description, issue_tx.description());
277        assert_eq!(proto.amount as u64, issue_tx.quantity());
278        assert_eq!(proto.decimals as u32, issue_tx.decimals());
279        assert_eq!(proto.reissuable, issue_tx.is_reissuable());
280        assert_eq!(proto.script, issue_tx.script().unwrap().bytes());
281        Ok(())
282    }
283
284    #[test]
285    fn test_issue_tx_to_json() -> Result<()> {
286        let issue_tx = &IssueTransaction::new(
287            "test asset".to_owned(),
288            "this is test asset".to_owned(),
289            32,
290            3,
291            false,
292            Some(Base64String::from_bytes(vec![1, 2, 3])),
293        );
294
295        let map: Map<String, Value> = issue_tx.try_into()?;
296        let json: Value = map.into();
297        let expected_json = json!({
298            "name": "test asset",
299            "quantity": 32,
300            "reissuable": false,
301            "decimals": 3,
302            "description": "this is test asset",
303            "script": "AQID",
304        });
305        assert_eq!(expected_json, json);
306        Ok(())
307    }
308
309    #[test]
310    fn test_min_fee_for_issue_transaction() {
311        let issue_transaction =
312            IssueTransaction::new("name".into(), "description".into(), 123, 8, true, None);
313
314        let nft_issue_transaction =
315            IssueTransaction::new("nft".into(), "description".into(), 1, 0, false, None);
316
317        assert_eq!(issue_transaction.min_fee().value(), 100_000_000);
318
319        assert!(nft_issue_transaction.is_nft_issue());
320        assert_eq!(nft_issue_transaction.min_fee().value(), 100_000);
321    }
322}