waves_rust/model/transaction/
mass_transfer_transaction.rs

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