waves_rust/model/transaction/
mass_transfer_transaction.rs1use 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}