waves-rust 0.2.3

A Rust library for interacting with the Waves blockchain. Supports node interaction, offline transaction signing and creating addresses and keys.
Documentation
use crate::error::{Error, Result};
use crate::model::{OrderInfo, SignedOrder};
use crate::util::JsonDeserializer;
use crate::waves_proto::ExchangeTransactionData;
use crate::waves_proto::Order as ProtoOrder;
use serde_json::{Map, Value};
use std::borrow::Borrow;

const TYPE: u8 = 7;

#[derive(Eq, PartialEq, Clone, Debug)]
pub struct ExchangeTransactionInfo {
    order1: OrderInfo,
    order2: OrderInfo,
    amount: u64,
    price: u64,
    buy_matcher_fee: u64,
    sell_matcher_fee: u64,
}

impl ExchangeTransactionInfo {
    pub fn order1(&self) -> OrderInfo {
        self.order1.clone()
    }

    pub fn order2(&self) -> OrderInfo {
        self.order2.clone()
    }

    pub fn amount(&self) -> u64 {
        self.amount
    }

    pub fn price(&self) -> u64 {
        self.price
    }

    pub fn buy_matcher_fee(&self) -> u64 {
        self.buy_matcher_fee
    }

    pub fn sell_matcher_fee(&self) -> u64 {
        self.sell_matcher_fee
    }

    pub fn tx_type() -> u8 {
        TYPE
    }
}

impl TryFrom<&Value> for ExchangeTransactionInfo {
    type Error = Error;

    fn try_from(exchange_tx_info_json: &Value) -> Result<Self> {
        let order1: OrderInfo = exchange_tx_info_json["order1"].borrow().try_into()?;
        let order2: OrderInfo = exchange_tx_info_json["order2"].borrow().try_into()?;
        let exchange_tx: ExchangeTransaction = exchange_tx_info_json.try_into()?;
        Ok(ExchangeTransactionInfo {
            order1,
            order2,
            amount: exchange_tx.amount,
            price: exchange_tx.price,
            buy_matcher_fee: exchange_tx.buy_matcher_fee,
            sell_matcher_fee: exchange_tx.sell_matcher_fee,
        })
    }
}

#[derive(Eq, PartialEq, Clone, Debug)]
pub struct ExchangeTransaction {
    order1: SignedOrder,
    order2: SignedOrder,
    amount: u64,
    price: u64,
    buy_matcher_fee: u64,
    sell_matcher_fee: u64,
}

impl ExchangeTransaction {
    pub fn new(
        order1: SignedOrder,
        order2: SignedOrder,
        amount: u64,
        price: u64,
        buy_matcher_fee: u64,
        sell_matcher_fee: u64,
    ) -> ExchangeTransaction {
        ExchangeTransaction {
            order1,
            order2,
            amount,
            price,
            buy_matcher_fee,
            sell_matcher_fee,
        }
    }

    pub fn order1(&self) -> SignedOrder {
        self.order1.clone()
    }

    pub fn order2(&self) -> SignedOrder {
        self.order2.clone()
    }

    pub fn amount(&self) -> u64 {
        self.amount
    }

    pub fn price(&self) -> u64 {
        self.price
    }

    pub fn buy_matcher_fee(&self) -> u64 {
        self.buy_matcher_fee
    }

    pub fn sell_matcher_fee(&self) -> u64 {
        self.sell_matcher_fee
    }

    pub fn tx_type() -> u8 {
        TYPE
    }
}

impl TryFrom<&Value> for ExchangeTransaction {
    type Error = Error;

    fn try_from(value: &Value) -> Result<Self> {
        let order1: SignedOrder = value["order1"].borrow().try_into()?;
        let order2: SignedOrder = value["order2"].borrow().try_into()?;
        let amount = JsonDeserializer::safe_to_int_from_field(value, "amount")?;
        let price = JsonDeserializer::safe_to_int_from_field(value, "price")?;
        let buy_matcher_fee = JsonDeserializer::safe_to_int_from_field(value, "buyMatcherFee")?;
        let sell_matcher_fee = JsonDeserializer::safe_to_int_from_field(value, "sellMatcherFee")?;
        Ok(ExchangeTransaction::new(
            order1,
            order2,
            amount as u64,
            price as u64,
            buy_matcher_fee as u64,
            sell_matcher_fee as u64,
        ))
    }
}

impl TryFrom<&ExchangeTransaction> for Map<String, Value> {
    type Error = Error;

    fn try_from(exchange_tx: &ExchangeTransaction) -> Result<Self> {
        let mut exchange_tx_json = Map::new();

        exchange_tx_json.insert("amount".to_owned(), exchange_tx.amount().into());
        exchange_tx_json.insert("price".to_owned(), exchange_tx.price().into());
        exchange_tx_json.insert(
            "buyMatcherFee".to_owned(),
            exchange_tx.buy_matcher_fee().into(),
        );
        exchange_tx_json.insert(
            "sellMatcherFee".to_owned(),
            exchange_tx.sell_matcher_fee().into(),
        );
        exchange_tx_json.insert("order1".to_owned(), exchange_tx.order1.borrow().try_into()?);
        exchange_tx_json.insert("order2".to_owned(), exchange_tx.order2.borrow().try_into()?);

        Ok(exchange_tx_json)
    }
}

impl TryFrom<&ExchangeTransaction> for ExchangeTransactionData {
    type Error = Error;

    fn try_from(exchange_tx: &ExchangeTransaction) -> Result<Self> {
        let order1: ProtoOrder = exchange_tx.order1().borrow().try_into()?;
        let order2: ProtoOrder = exchange_tx.order2().borrow().try_into()?;
        Ok(ExchangeTransactionData {
            amount: exchange_tx.amount as i64,
            price: exchange_tx.price as i64,
            buy_matcher_fee: exchange_tx.buy_matcher_fee as i64,
            sell_matcher_fee: exchange_tx.sell_matcher_fee as i64,
            orders: vec![order1, order2],
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::model::{
        v4, Amount, AssetId, ByteString, ExchangeTransaction, ExchangeTransactionInfo, Order,
        OrderType, PriceMode, Proof, PublicKey, SignedOrder,
    };

    use crate::error::Result;
    use crate::waves_proto::order::Sender;
    use crate::waves_proto::ExchangeTransactionData;
    use serde_json::{json, Map, Value};
    use std::borrow::Borrow;
    use std::fs;

    #[test]
    fn test_exchange_tx_from_json() {
        let data =
            fs::read_to_string("./tests/resources/exchange_rs.json").expect("Unable to read file");
        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
        let exchange_tx_from_json: ExchangeTransactionInfo = json.borrow().try_into().unwrap();

        // order1
        let order_info1 = exchange_tx_from_json.order1();
        let chain_id = order_info1.chain_id();
        assert_eq!(4, order_info1.version());
        assert_eq!(
            "3DCDNkx3iw9UBhKfQgibxrCes1uXPeMaexpgf5kQyz18",
            order_info1.id().encoded()
        );
        assert_eq!(
            "3MzpbTjjF1ng9aLWSkq96JktGRs1FDVuDSk",
            order_info1
                .sender()
                .address(chain_id)
                .expect("failed to get address")
                .encoded()
        );
        assert_eq!(
            "BDSyopLzAjMYvQSm4XuMA2gtjP5TPoZMWQ1sxnzTE1Y8",
            order_info1.sender().encoded()
        );
        assert_eq!(
            "8QUAqtTckM5B8gvcuP7mMswat9SjKUuafJMusEoSn1Gy",
            order_info1.matcher().encoded()
        );
        assert_eq!(None, order_info1.amount().asset_id());
        assert_eq!(660949620, order_info1.amount().value());
        assert_eq!(
            "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
            order_info1
                .price()
                .asset_id()
                .expect("asset must be non empty")
                .encoded()
        );
        assert_eq!(15000000, order_info1.price().value());
        assert_eq!(OrderType::Buy, order_info1.order_type());
        assert_eq!(1666571041063, order_info1.timestamp());
        assert_eq!(1669080241063, order_info1.expiration());
        assert_eq!(99143, order_info1.fee().value());
        assert_eq!(
            "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
            order_info1
                .fee()
                .asset_id()
                .expect("asset must be non empty")
                .encoded()
        );
        assert_eq!("3GmuCwFTs5jcJjerkZP28aEAvFV1qqJx9QTjC9dVBVrYeqJ9pqoaB1vU1ieZmZFXTcD6jSr7JLDsKbsLKZtgcBpm", &order_info1.proofs()[0].encoded());

        // order2
        let order_info2 = exchange_tx_from_json.order2();
        let chain_id = order_info2.chain_id();
        assert_eq!(3, order_info2.version());
        assert_eq!(
            "H2EaCndcFAETGaWkPifGdNBL3scaZ53Pgm4Ha4xvg9wb",
            order_info2.id().encoded()
        );
        assert_eq!(
            "3My6wXYDaS6C86Zk3qToU8Lv24G4ueEXHcd",
            order_info2
                .sender()
                .address(chain_id)
                .expect("failed to get address")
                .encoded()
        );
        assert_eq!(
            "FarW7tFmnVJBsHUdDe9DMJcfUESh266UDmEm1vP6P2xE",
            order_info2.sender().encoded()
        );
        assert_eq!(
            "8QUAqtTckM5B8gvcuP7mMswat9SjKUuafJMusEoSn1Gy",
            order_info2.matcher().encoded()
        );
        assert_eq!(None, order_info1.amount().asset_id());
        assert_eq!(660949620, order_info1.amount().value());
        assert_eq!(
            "25FEqEjRkqK6yCkiT7Lz6SAYz7gUFCtxfCChnrVFD5AT",
            order_info1
                .price()
                .asset_id()
                .expect("asset must be non empty")
                .encoded()
        );
        assert_eq!(15000000, order_info1.price().value());
        assert_eq!(OrderType::Sell, order_info2.order_type());
        assert_eq!(1664244861345, order_info2.timestamp());
        assert_eq!(1666750461345, order_info2.expiration());
        assert_eq!(10000000, order_info2.fee().value());
        assert_eq!(None, order_info2.fee().asset_id());
        assert_eq!("38rb8vVaYR4iqfTLvHPEQ83kkhtwjcTP4f8p8A1tSquzNF41m78GEN5Qr3Lc3k8fzeGTuV1oiPTVkoAjGvrYvmpN", &order_info2.proofs()[0].encoded());

        // tx
        assert_eq!(640949620, exchange_tx_from_json.amount());
        assert_eq!(1500000000, exchange_tx_from_json.price());
        assert_eq!(96142, exchange_tx_from_json.buy_matcher_fee());
        assert_eq!(640949, exchange_tx_from_json.sell_matcher_fee());
    }

    #[test]
    fn test_exchange_transaction_to_json() -> Result<()> {
        let buy_order = SignedOrder::new(
            Order::V4(v4::OrderV4::new(
            84,
            1662500994929,
            PublicKey::from_string("9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh")?,
            Amount::new(300000, None),
            OrderType::Buy,
            Amount::new(
                100,
                Some(AssetId::from_string(
                    "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                )?),
            ),
            Amount::new(1000, None),
            PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
            1665092994929,
            PriceMode::Default,
            )),
            vec![Proof::from_string("2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8")?],
        );

        let sell_order = SignedOrder::new(Order::V4(v4::OrderV4::new(
            84,
            1662500994931,
            PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
            Amount::new(300000, None),
            OrderType::Sell,
            Amount::new(
                100,
                Some(AssetId::from_string(
                    "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                )?),
            ),
            Amount::new(1000, None),
            PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
            1665092994931,
            PriceMode::Default,
            )),
        vec![Proof::from_string("5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL")?]
        );

        let exchange_transaction =
            &ExchangeTransaction::new(buy_order, sell_order, 100, 1000, 300000, 300000);
        let map: Map<String, Value> = exchange_transaction.try_into()?;
        let json: Value = map.into();

        let expected_json = json!({
            "order1": {
                "version": 4,
                "sender": "3MxjhrvCr1nnDxvNJiCQfSC557gd8QYEhDx",
                "senderPublicKey": "9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh",
                "matcherPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
                "assetPair": {
                  "amountAsset": "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                  "priceAsset": null
                },
                "orderType": "buy",
                "amount": 100,
                "price": 1000,
                "timestamp": 1662500994929_i64,
                "expiration": 1665092994929_i64,
                "matcherFee": 300000,
                "signature": "2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8",
                "proofs": [
                  "2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8"
                ],
                "matcherFeeAssetId": null,
                "priceMode": "default"
            },
            "order2": {
              "version": 4,
              "sender": "3MxtrLkrbcG28uTvmbKmhrwGrR65ooHVYvK",
              "senderPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
              "matcherPublicKey": "CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt",
              "assetPair": {
                "amountAsset": "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                "priceAsset": null
              },
              "orderType": "sell",
              "amount": 100,
              "price": 1000,
              "timestamp": 1662500994931_i64,
              "expiration": 1665092994931_i64,
              "matcherFee": 300000,
              "signature": "5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL",
              "proofs": [
                "5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL"
              ],
              "matcherFeeAssetId": null,
              "priceMode": "default"
            },
            "amount": 100,
            "price": 1000,
            "buyMatcherFee": 300000,
            "sellMatcherFee": 300000
        });

        assert_eq!(expected_json, json);

        Ok(())
    }

    #[test]
    fn test_exchange_transaction_to_proto() -> Result<()> {
        let buy_order = SignedOrder::new(
            Order::V4(v4::OrderV4::new(
                84,
                1662500994929,
                PublicKey::from_string("9oRf59sSHE2inwF6wraJDPQNsx7ktMKxaKvyFFL8GDrh")?,
                Amount::new(300000, None),
                OrderType::Buy,
                Amount::new(
                    100,
                    Some(AssetId::from_string(
                        "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                    )?),
                ),
                Amount::new(1000, None),
                PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
                1665092994929,
                PriceMode::Default
            )),
            vec![Proof::from_string("2YgYwW6o88K3NXYy39TaUu1bwVkzpbr9oQwSDehnkJskfshC6f9F5vYmY736kEExRGHiDmW4hbuyxuqE8cw8WeJ8")?]
        );

        let sell_order = SignedOrder::new(Order::V4(v4::OrderV4::new(
            84,
            1662500994931,
            PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
            Amount::new(300000, None),
            OrderType::Sell,
            Amount::new(
                100,
                Some(AssetId::from_string(
                    "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
                )?),
            ),
            Amount::new(1000, None),
            PublicKey::from_string("CJJu3U5UL35Dhq5KGRZw2rdundAv2pPgB7GF21G3y4vt")?,
            1665092994931,
            PriceMode::Default
        )),
           vec![Proof::from_string("5Mbvg4kz1rPLBVBWoTcY2e6Zajoqxq6g38WPfvxCMiHmjxm8TPZpLpEitf9SdfGSpBHtAxas2YRe7X4UcmBugDFL")?]
        );

        let exchange_transaction =
            &ExchangeTransaction::new(buy_order, sell_order, 100, 1000, 300000, 300000);
        let proto: ExchangeTransactionData = exchange_transaction.try_into()?;

        assert_eq!(exchange_transaction.amount(), proto.amount as u64);
        assert_eq!(
            exchange_transaction.sell_matcher_fee(),
            proto.sell_matcher_fee as u64
        );
        assert_eq!(exchange_transaction.price(), proto.price as u64);
        assert_eq!(
            exchange_transaction.buy_matcher_fee(),
            proto.buy_matcher_fee as u64
        );

        let buy_order_proto = &proto.orders[0];
        let buy_order = &exchange_transaction.order1.order();
        assert_eq!(buy_order.chain_id(), buy_order_proto.chain_id as u8);
        assert_eq!(buy_order.version(), buy_order_proto.version as u8);
        assert_eq!(buy_order.timestamp(), buy_order_proto.timestamp as u64);
        let proto_sender =
            if let Sender::SenderPublicKey(bytes) = buy_order_proto.clone().sender.unwrap() {
                bytes
            } else {
                panic!("expected sender public key")
            };
        assert_eq!(buy_order.sender().bytes(), proto_sender);
        assert_eq!(buy_order.amount().value(), buy_order_proto.amount as u64);
        assert_eq!(
            buy_order.fee().value(),
            buy_order_proto.clone().matcher_fee.unwrap().amount as u64
        );
        assert_eq!(
            buy_order
                .fee()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            buy_order_proto.clone().matcher_fee.unwrap().asset_id
        );

        assert_eq!(
            match buy_order.order_type() {
                OrderType::Buy => 0,
                OrderType::Sell => 1,
            },
            buy_order_proto.order_side
        );

        assert_eq!(buy_order.amount().value(), buy_order_proto.amount as u64);
        assert_eq!(
            buy_order
                .amount()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            buy_order_proto.clone().asset_pair.unwrap().amount_asset_id
        );

        assert_eq!(buy_order.price().value(), buy_order_proto.price as u64);
        assert_eq!(
            buy_order
                .price()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            buy_order_proto.clone().asset_pair.unwrap().price_asset_id
        );

        assert_eq!(
            buy_order.matcher().bytes(),
            buy_order_proto.matcher_public_key
        );
        assert_eq!(
            exchange_transaction.order1.proofs()[0].bytes(),
            buy_order_proto.proofs[0]
        );

        if let Order::V4(order_v4) = buy_order {
            assert_eq!(
                match order_v4.price_mode() {
                    PriceMode::Default => 0,
                    PriceMode::FixedDecimals => 1,
                    PriceMode::AssetDecimals => 1,
                },
                buy_order_proto.price_mode
            );
        }

        let sell_order_proto = &proto.orders[1];
        let sell_order = &exchange_transaction.order2().order();
        assert_eq!(sell_order.chain_id(), sell_order_proto.chain_id as u8);
        assert_eq!(sell_order.version(), sell_order_proto.version as u8);
        assert_eq!(sell_order.timestamp(), sell_order_proto.timestamp as u64);
        let proto_sender =
            if let Sender::SenderPublicKey(bytes) = sell_order_proto.clone().sender.unwrap() {
                bytes
            } else {
                panic!("expected sender public key")
            };
        assert_eq!(sell_order.sender().bytes(), proto_sender);
        assert_eq!(sell_order.amount().value(), sell_order_proto.amount as u64);
        assert_eq!(
            sell_order.fee().value(),
            sell_order_proto.clone().matcher_fee.unwrap().amount as u64
        );
        assert_eq!(
            sell_order
                .fee()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            sell_order_proto.clone().matcher_fee.unwrap().asset_id
        );

        assert_eq!(
            match sell_order.order_type() {
                OrderType::Buy => 0,
                OrderType::Sell => 1,
            },
            sell_order_proto.order_side
        );

        assert_eq!(sell_order.amount().value(), sell_order_proto.amount as u64);
        assert_eq!(
            sell_order
                .amount()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            sell_order_proto.clone().asset_pair.unwrap().amount_asset_id
        );

        assert_eq!(sell_order.price().value(), sell_order_proto.price as u64);
        assert_eq!(
            sell_order
                .price()
                .asset_id()
                .map(|it| it.bytes())
                .unwrap_or_default(),
            sell_order_proto.clone().asset_pair.unwrap().price_asset_id
        );

        assert_eq!(
            sell_order.matcher().bytes(),
            sell_order_proto.matcher_public_key
        );
        assert_eq!(
            exchange_transaction.order2.proofs()[0].bytes(),
            sell_order_proto.proofs[0]
        );

        if let Order::V4(order_v4) = sell_order {
            assert_eq!(
                match order_v4.price_mode() {
                    PriceMode::Default => 0,
                    PriceMode::FixedDecimals => 1,
                    PriceMode::AssetDecimals => 1,
                },
                sell_order_proto.price_mode
            );
        }

        Ok(())
    }
}