1use crate::error::{Error, Result};
2use crate::model::{OrderInfo, SignedOrder};
3use crate::util::JsonDeserializer;
4use crate::waves_proto::ExchangeTransactionData;
5use crate::waves_proto::Order as ProtoOrder;
6use serde_json::{Map, Value};
7use std::borrow::Borrow;
8
9const TYPE: u8 = 7;
10
11#[derive(Eq, PartialEq, Clone, Debug)]
12pub struct ExchangeTransactionInfo {
13 order1: OrderInfo,
14 order2: OrderInfo,
15 amount: u64,
16 price: u64,
17 buy_matcher_fee: u64,
18 sell_matcher_fee: u64,
19}
20
21impl ExchangeTransactionInfo {
22 pub fn order1(&self) -> OrderInfo {
23 self.order1.clone()
24 }
25
26 pub fn order2(&self) -> OrderInfo {
27 self.order2.clone()
28 }
29
30 pub fn amount(&self) -> u64 {
31 self.amount
32 }
33
34 pub fn price(&self) -> u64 {
35 self.price
36 }
37
38 pub fn buy_matcher_fee(&self) -> u64 {
39 self.buy_matcher_fee
40 }
41
42 pub fn sell_matcher_fee(&self) -> u64 {
43 self.sell_matcher_fee
44 }
45
46 pub fn tx_type() -> u8 {
47 TYPE
48 }
49}
50
51impl TryFrom<&Value> for ExchangeTransactionInfo {
52 type Error = Error;
53
54 fn try_from(exchange_tx_info_json: &Value) -> Result<Self> {
55 let order1: OrderInfo = exchange_tx_info_json["order1"].borrow().try_into()?;
56 let order2: OrderInfo = exchange_tx_info_json["order2"].borrow().try_into()?;
57 let exchange_tx: ExchangeTransaction = exchange_tx_info_json.try_into()?;
58 Ok(ExchangeTransactionInfo {
59 order1,
60 order2,
61 amount: exchange_tx.amount,
62 price: exchange_tx.price,
63 buy_matcher_fee: exchange_tx.buy_matcher_fee,
64 sell_matcher_fee: exchange_tx.sell_matcher_fee,
65 })
66 }
67}
68
69#[derive(Eq, PartialEq, Clone, Debug)]
70pub struct ExchangeTransaction {
71 order1: SignedOrder,
72 order2: SignedOrder,
73 amount: u64,
74 price: u64,
75 buy_matcher_fee: u64,
76 sell_matcher_fee: u64,
77}
78
79impl ExchangeTransaction {
80 pub fn new(
81 order1: SignedOrder,
82 order2: SignedOrder,
83 amount: u64,
84 price: u64,
85 buy_matcher_fee: u64,
86 sell_matcher_fee: u64,
87 ) -> ExchangeTransaction {
88 ExchangeTransaction {
89 order1,
90 order2,
91 amount,
92 price,
93 buy_matcher_fee,
94 sell_matcher_fee,
95 }
96 }
97
98 pub fn order1(&self) -> SignedOrder {
99 self.order1.clone()
100 }
101
102 pub fn order2(&self) -> SignedOrder {
103 self.order2.clone()
104 }
105
106 pub fn amount(&self) -> u64 {
107 self.amount
108 }
109
110 pub fn price(&self) -> u64 {
111 self.price
112 }
113
114 pub fn buy_matcher_fee(&self) -> u64 {
115 self.buy_matcher_fee
116 }
117
118 pub fn sell_matcher_fee(&self) -> u64 {
119 self.sell_matcher_fee
120 }
121
122 pub fn tx_type() -> u8 {
123 TYPE
124 }
125}
126
127impl TryFrom<&Value> for ExchangeTransaction {
128 type Error = Error;
129
130 fn try_from(value: &Value) -> Result<Self> {
131 let order1: SignedOrder = value["order1"].borrow().try_into()?;
132 let order2: SignedOrder = value["order2"].borrow().try_into()?;
133 let amount = JsonDeserializer::safe_to_int_from_field(value, "amount")?;
134 let price = JsonDeserializer::safe_to_int_from_field(value, "price")?;
135 let buy_matcher_fee = JsonDeserializer::safe_to_int_from_field(value, "buyMatcherFee")?;
136 let sell_matcher_fee = JsonDeserializer::safe_to_int_from_field(value, "sellMatcherFee")?;
137 Ok(ExchangeTransaction::new(
138 order1,
139 order2,
140 amount as u64,
141 price as u64,
142 buy_matcher_fee as u64,
143 sell_matcher_fee as u64,
144 ))
145 }
146}
147
148impl TryFrom<&ExchangeTransaction> for Map<String, Value> {
149 type Error = Error;
150
151 fn try_from(exchange_tx: &ExchangeTransaction) -> Result<Self> {
152 let mut exchange_tx_json = Map::new();
153
154 exchange_tx_json.insert("amount".to_owned(), exchange_tx.amount().into());
155 exchange_tx_json.insert("price".to_owned(), exchange_tx.price().into());
156 exchange_tx_json.insert(
157 "buyMatcherFee".to_owned(),
158 exchange_tx.buy_matcher_fee().into(),
159 );
160 exchange_tx_json.insert(
161 "sellMatcherFee".to_owned(),
162 exchange_tx.sell_matcher_fee().into(),
163 );
164 exchange_tx_json.insert("order1".to_owned(), exchange_tx.order1.borrow().try_into()?);
165 exchange_tx_json.insert("order2".to_owned(), exchange_tx.order2.borrow().try_into()?);
166
167 Ok(exchange_tx_json)
168 }
169}
170
171impl TryFrom<&ExchangeTransaction> for ExchangeTransactionData {
172 type Error = Error;
173
174 fn try_from(exchange_tx: &ExchangeTransaction) -> Result<Self> {
175 let order1: ProtoOrder = exchange_tx.order1().borrow().try_into()?;
176 let order2: ProtoOrder = exchange_tx.order2().borrow().try_into()?;
177 Ok(ExchangeTransactionData {
178 amount: exchange_tx.amount as i64,
179 price: exchange_tx.price as i64,
180 buy_matcher_fee: exchange_tx.buy_matcher_fee as i64,
181 sell_matcher_fee: exchange_tx.sell_matcher_fee as i64,
182 orders: vec![order1, order2],
183 })
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use crate::model::{
190 v4, Amount, AssetId, ByteString, ExchangeTransaction, ExchangeTransactionInfo, Order,
191 OrderType, PriceMode, Proof, PublicKey, SignedOrder,
192 };
193
194 use crate::error::Result;
195 use crate::waves_proto::order::Sender;
196 use crate::waves_proto::ExchangeTransactionData;
197 use serde_json::{json, Map, Value};
198 use std::borrow::Borrow;
199 use std::fs;
200
201 #[test]
202 fn test_exchange_tx_from_json() {
203 let data =
204 fs::read_to_string("./tests/resources/exchange_rs.json").expect("Unable to read file");
205 let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
206 let exchange_tx_from_json: ExchangeTransactionInfo = json.borrow().try_into().unwrap();
207
208 let order_info1 = exchange_tx_from_json.order1();
210 let chain_id = order_info1.chain_id();
211 assert_eq!(4, order_info1.version());
212 assert_eq!(
213 "3DCDNkx3iw9UBhKfQgibxrCes1uXPeMaexpgf5kQyz18",
214 order_info1.id().encoded()
215 );
216 assert_eq!(
217 "3MzpbTjjF1ng9aLWSkq96JktGRs1FDVuDSk",
218 order_info1
219 .sender()
220 .address(chain_id)
221 .expect("failed to get address")
222 .encoded()
223 );
224 assert_eq!(
225 "BDSyopLzAjMYvQSm4XuMA2gtjP5TPoZMWQ1sxnzTE1Y8",
226 order_info1.sender().encoded()
227 );
228 assert_eq!(
229 "8QUAqtTckM5B8gvcuP7mMswat9SjKUuafJMusEoSn1Gy",
230 order_info1.matcher().encoded()
231 );
232 assert_eq!(None, order_info1.amount().asset_id());
233 assert_eq!(660949620, order_info1.amount().value());
234 assert_eq!(
235 "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
236 order_info1
237 .price()
238 .asset_id()
239 .expect("asset must be non empty")
240 .encoded()
241 );
242 assert_eq!(15000000, order_info1.price().value());
243 assert_eq!(OrderType::Buy, order_info1.order_type());
244 assert_eq!(1666571041063, order_info1.timestamp());
245 assert_eq!(1669080241063, order_info1.expiration());
246 assert_eq!(99143, order_info1.fee().value());
247 assert_eq!(
248 "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
249 order_info1
250 .fee()
251 .asset_id()
252 .expect("asset must be non empty")
253 .encoded()
254 );
255 assert_eq!("3GmuCwFTs5jcJjerkZP28aEAvFV1qqJx9QTjC9dVBVrYeqJ9pqoaB1vU1ieZmZFXTcD6jSr7JLDsKbsLKZtgcBpm", &order_info1.proofs()[0].encoded());
256
257 let order_info2 = exchange_tx_from_json.order2();
259 let chain_id = order_info2.chain_id();
260 assert_eq!(3, order_info2.version());
261 assert_eq!(
262 "H2EaCndcFAETGaWkPifGdNBL3scaZ53Pgm4Ha4xvg9wb",
263 order_info2.id().encoded()
264 );
265 assert_eq!(
266 "3My6wXYDaS6C86Zk3qToU8Lv24G4ueEXHcd",
267 order_info2
268 .sender()
269 .address(chain_id)
270 .expect("failed to get address")
271 .encoded()
272 );
273 assert_eq!(
274 "FarW7tFmnVJBsHUdDe9DMJcfUESh266UDmEm1vP6P2xE",
275 order_info2.sender().encoded()
276 );
277 assert_eq!(
278 "8QUAqtTckM5B8gvcuP7mMswat9SjKUuafJMusEoSn1Gy",
279 order_info2.matcher().encoded()
280 );
281 assert_eq!(None, order_info1.amount().asset_id());
282 assert_eq!(660949620, order_info1.amount().value());
283 assert_eq!(
284 "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
285 order_info1
286 .price()
287 .asset_id()
288 .expect("asset must be non empty")
289 .encoded()
290 );
291 assert_eq!(15000000, order_info1.price().value());
292 assert_eq!(OrderType::Sell, order_info2.order_type());
293 assert_eq!(1664244861345, order_info2.timestamp());
294 assert_eq!(1666750461345, order_info2.expiration());
295 assert_eq!(10000000, order_info2.fee().value());
296 assert_eq!(None, order_info2.fee().asset_id());
297 assert_eq!("38rb8vVaYR4iqfTLvHPEQ83kkhtwjcTP4f8p8A1tSquzNF41m78GEN5Qr3Lc3k8fzeGTuV1oiPTVkoAjGvrYvmpN", &order_info2.proofs()[0].encoded());
298
299 assert_eq!(640949620, exchange_tx_from_json.amount());
301 assert_eq!(1500000000, exchange_tx_from_json.price());
302 assert_eq!(96142, exchange_tx_from_json.buy_matcher_fee());
303 assert_eq!(640949, exchange_tx_from_json.sell_matcher_fee());
304 }
305
306 #[test]
307 fn test_exchange_transaction_to_json() -> Result<()> {
308 let buy_order = SignedOrder::new(
309 Order::V4(v4::OrderV4::new(
310 84,
311 1662500994929,
312 PublicKey::from_string("9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh")?,
313 Amount::new(300000, None),
314 OrderType::Buy,
315 Amount::new(
316 100,
317 Some(AssetId::from_string(
318 "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
319 )?),
320 ),
321 Amount::new(1000, None),
322 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
323 1665092994929,
324 PriceMode::Default,
325 )),
326 vec![Proof::from_string("2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8")?],
327 );
328
329 let sell_order = SignedOrder::new(Order::V4(v4::OrderV4::new(
330 84,
331 1662500994931,
332 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
333 Amount::new(300000, None),
334 OrderType::Sell,
335 Amount::new(
336 100,
337 Some(AssetId::from_string(
338 "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
339 )?),
340 ),
341 Amount::new(1000, None),
342 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
343 1665092994931,
344 PriceMode::Default,
345 )),
346 vec![Proof::from_string("5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL")?]
347 );
348
349 let exchange_transaction =
350 &ExchangeTransaction::new(buy_order, sell_order, 100, 1000, 300000, 300000);
351 let map: Map<String, Value> = exchange_transaction.try_into()?;
352 let json: Value = map.into();
353
354 let expected_json = json!({
355 "order1": {
356 "version": 4,
357 "sender": "3MxjhrvCr1nnDxvNJiCQfSC557gd8QYEhDx",
358 "senderPublicKey": "9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh",
359 "matcherPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
360 "assetPair": {
361 "amountAsset": "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
362 "priceAsset": null
363 },
364 "orderType": "buy",
365 "amount": 100,
366 "price": 1000,
367 "timestamp": 1662500994929_i64,
368 "expiration": 1665092994929_i64,
369 "matcherFee": 300000,
370 "signature": "2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8",
371 "proofs": [
372 "2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8"
373 ],
374 "matcherFeeAssetId": null,
375 "priceMode": "default"
376 },
377 "order2": {
378 "version": 4,
379 "sender": "3MxtrLkrbcG28uTvmbKmhrwGrR65ooHVYvK",
380 "senderPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
381 "matcherPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
382 "assetPair": {
383 "amountAsset": "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
384 "priceAsset": null
385 },
386 "orderType": "sell",
387 "amount": 100,
388 "price": 1000,
389 "timestamp": 1662500994931_i64,
390 "expiration": 1665092994931_i64,
391 "matcherFee": 300000,
392 "signature": "5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL",
393 "proofs": [
394 "5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL"
395 ],
396 "matcherFeeAssetId": null,
397 "priceMode": "default"
398 },
399 "amount": 100,
400 "price": 1000,
401 "buyMatcherFee": 300000,
402 "sellMatcherFee": 300000
403 });
404
405 assert_eq!(expected_json, json);
406
407 Ok(())
408 }
409
410 #[test]
411 fn test_exchange_transaction_to_proto() -> Result<()> {
412 let buy_order = SignedOrder::new(
413 Order::V4(v4::OrderV4::new(
414 84,
415 1662500994929,
416 PublicKey::from_string("9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh")?,
417 Amount::new(300000, None),
418 OrderType::Buy,
419 Amount::new(
420 100,
421 Some(AssetId::from_string(
422 "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
423 )?),
424 ),
425 Amount::new(1000, None),
426 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
427 1665092994929,
428 PriceMode::Default
429 )),
430 vec![Proof::from_string("2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8")?]
431 );
432
433 let sell_order = SignedOrder::new(Order::V4(v4::OrderV4::new(
434 84,
435 1662500994931,
436 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
437 Amount::new(300000, None),
438 OrderType::Sell,
439 Amount::new(
440 100,
441 Some(AssetId::from_string(
442 "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
443 )?),
444 ),
445 Amount::new(1000, None),
446 PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
447 1665092994931,
448 PriceMode::Default
449 )),
450 vec![Proof::from_string("5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL")?]
451 );
452
453 let exchange_transaction =
454 &ExchangeTransaction::new(buy_order, sell_order, 100, 1000, 300000, 300000);
455 let proto: ExchangeTransactionData = exchange_transaction.try_into()?;
456
457 assert_eq!(exchange_transaction.amount(), proto.amount as u64);
458 assert_eq!(
459 exchange_transaction.sell_matcher_fee(),
460 proto.sell_matcher_fee as u64
461 );
462 assert_eq!(exchange_transaction.price(), proto.price as u64);
463 assert_eq!(
464 exchange_transaction.buy_matcher_fee(),
465 proto.buy_matcher_fee as u64
466 );
467
468 let buy_order_proto = &proto.orders[0];
469 let buy_order = &exchange_transaction.order1.order();
470 assert_eq!(buy_order.chain_id(), buy_order_proto.chain_id as u8);
471 assert_eq!(buy_order.version(), buy_order_proto.version as u8);
472 assert_eq!(buy_order.timestamp(), buy_order_proto.timestamp as u64);
473 let proto_sender =
474 if let Sender::SenderPublicKey(bytes) = buy_order_proto.clone().sender.unwrap() {
475 bytes
476 } else {
477 panic!("expected sender public key")
478 };
479 assert_eq!(buy_order.sender().bytes(), proto_sender);
480 assert_eq!(buy_order.amount().value(), buy_order_proto.amount as u64);
481 assert_eq!(
482 buy_order.fee().value(),
483 buy_order_proto.clone().matcher_fee.unwrap().amount as u64
484 );
485 assert_eq!(
486 buy_order
487 .fee()
488 .asset_id()
489 .map(|it| it.bytes())
490 .unwrap_or_default(),
491 buy_order_proto.clone().matcher_fee.unwrap().asset_id
492 );
493
494 assert_eq!(
495 match buy_order.order_type() {
496 OrderType::Buy => 0,
497 OrderType::Sell => 1,
498 },
499 buy_order_proto.order_side
500 );
501
502 assert_eq!(buy_order.amount().value(), buy_order_proto.amount as u64);
503 assert_eq!(
504 buy_order
505 .amount()
506 .asset_id()
507 .map(|it| it.bytes())
508 .unwrap_or_default(),
509 buy_order_proto.clone().asset_pair.unwrap().amount_asset_id
510 );
511
512 assert_eq!(buy_order.price().value(), buy_order_proto.price as u64);
513 assert_eq!(
514 buy_order
515 .price()
516 .asset_id()
517 .map(|it| it.bytes())
518 .unwrap_or_default(),
519 buy_order_proto.clone().asset_pair.unwrap().price_asset_id
520 );
521
522 assert_eq!(
523 buy_order.matcher().bytes(),
524 buy_order_proto.matcher_public_key
525 );
526 assert_eq!(
527 exchange_transaction.order1.proofs()[0].bytes(),
528 buy_order_proto.proofs[0]
529 );
530
531 if let Order::V4(order_v4) = buy_order {
532 assert_eq!(
533 match order_v4.price_mode() {
534 PriceMode::Default => 0,
535 PriceMode::FixedDecimals => 1,
536 PriceMode::AssetDecimals => 1,
537 },
538 buy_order_proto.price_mode
539 );
540 }
541
542 let sell_order_proto = &proto.orders[1];
543 let sell_order = &exchange_transaction.order2().order();
544 assert_eq!(sell_order.chain_id(), sell_order_proto.chain_id as u8);
545 assert_eq!(sell_order.version(), sell_order_proto.version as u8);
546 assert_eq!(sell_order.timestamp(), sell_order_proto.timestamp as u64);
547 let proto_sender =
548 if let Sender::SenderPublicKey(bytes) = sell_order_proto.clone().sender.unwrap() {
549 bytes
550 } else {
551 panic!("expected sender public key")
552 };
553 assert_eq!(sell_order.sender().bytes(), proto_sender);
554 assert_eq!(sell_order.amount().value(), sell_order_proto.amount as u64);
555 assert_eq!(
556 sell_order.fee().value(),
557 sell_order_proto.clone().matcher_fee.unwrap().amount as u64
558 );
559 assert_eq!(
560 sell_order
561 .fee()
562 .asset_id()
563 .map(|it| it.bytes())
564 .unwrap_or_default(),
565 sell_order_proto.clone().matcher_fee.unwrap().asset_id
566 );
567
568 assert_eq!(
569 match sell_order.order_type() {
570 OrderType::Buy => 0,
571 OrderType::Sell => 1,
572 },
573 sell_order_proto.order_side
574 );
575
576 assert_eq!(sell_order.amount().value(), sell_order_proto.amount as u64);
577 assert_eq!(
578 sell_order
579 .amount()
580 .asset_id()
581 .map(|it| it.bytes())
582 .unwrap_or_default(),
583 sell_order_proto.clone().asset_pair.unwrap().amount_asset_id
584 );
585
586 assert_eq!(sell_order.price().value(), sell_order_proto.price as u64);
587 assert_eq!(
588 sell_order
589 .price()
590 .asset_id()
591 .map(|it| it.bytes())
592 .unwrap_or_default(),
593 sell_order_proto.clone().asset_pair.unwrap().price_asset_id
594 );
595
596 assert_eq!(
597 sell_order.matcher().bytes(),
598 sell_order_proto.matcher_public_key
599 );
600 assert_eq!(
601 exchange_transaction.order2.proofs()[0].bytes(),
602 sell_order_proto.proofs[0]
603 );
604
605 if let Order::V4(order_v4) = sell_order {
606 assert_eq!(
607 match order_v4.price_mode() {
608 PriceMode::Default => 0,
609 PriceMode::FixedDecimals => 1,
610 PriceMode::AssetDecimals => 1,
611 },
612 sell_order_proto.price_mode
613 );
614 }
615
616 Ok(())
617 }
618}