waves_rust/model/transaction/
exchange_transaction.rs

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        // order1
209        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        // order2
258        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        // tx
300        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}