waves_rust/model/transaction/
transfer_transaction.rs

1use crate::error::{Error, Result};
2use crate::model::{Address, Amount, AssetId, Base58String, ByteString};
3use crate::util::{Base58, JsonDeserializer};
4use crate::waves_proto::{recipient, Amount as ProtoAmount, Recipient, TransferTransactionData};
5use serde_json::{Map, Value};
6
7const TYPE: u8 = 4;
8
9#[derive(Clone, Eq, PartialEq, Debug)]
10pub struct TransferTransactionInfo {
11    recipient: Address,
12    amount: Amount,
13    attachment: Base58String,
14}
15
16impl TransferTransactionInfo {
17    pub fn attachment(&self) -> Base58String {
18        self.attachment.clone()
19    }
20
21    pub fn amount(&self) -> Amount {
22        self.amount.clone()
23    }
24
25    pub fn recipient(&self) -> Address {
26        self.recipient.clone()
27    }
28}
29
30impl TryFrom<&Value> for TransferTransactionInfo {
31    type Error = Error;
32
33    fn try_from(value: &Value) -> Result<Self> {
34        let transfer_transaction: TransferTransaction = value.try_into()?;
35        Ok(TransferTransactionInfo {
36            recipient: transfer_transaction.recipient(),
37            amount: transfer_transaction.amount(),
38            attachment: transfer_transaction.attachment(),
39        })
40    }
41}
42
43#[derive(Clone, Eq, PartialEq, Debug)]
44pub struct TransferTransaction {
45    recipient: Address,
46    amount: Amount,
47    attachment: Base58String,
48}
49
50impl TransferTransaction {
51    pub fn new(
52        recipient: Address,
53        amount: Amount,
54        attachment: Base58String,
55    ) -> TransferTransaction {
56        TransferTransaction {
57            recipient,
58            amount,
59            attachment,
60        }
61    }
62
63    pub fn recipient(&self) -> Address {
64        self.recipient.clone()
65    }
66
67    pub fn amount(&self) -> Amount {
68        self.amount.clone()
69    }
70
71    pub fn attachment(&self) -> Base58String {
72        self.attachment.clone()
73    }
74
75    pub fn tx_type() -> u8 {
76        TYPE
77    }
78}
79
80impl TryFrom<&TransferTransaction> for TransferTransactionData {
81    type Error = Error;
82
83    fn try_from(transfer_tx: &TransferTransaction) -> Result<Self> {
84        let recipient = Some(Recipient {
85            recipient: Some(recipient::Recipient::PublicKeyHash(
86                transfer_tx.recipient().public_key_hash(),
87            )),
88        });
89        let asset_id = match transfer_tx.amount().asset_id() {
90            Some(value) => value.bytes(),
91            None => vec![],
92        };
93        let amount = Some(ProtoAmount {
94            asset_id,
95            amount: transfer_tx.amount().value() as i64,
96        });
97        Ok(TransferTransactionData {
98            recipient,
99            amount,
100            attachment: transfer_tx.attachment().bytes(),
101        })
102    }
103}
104
105impl TryFrom<&Value> for TransferTransaction {
106    type Error = Error;
107
108    fn try_from(value: &Value) -> std::result::Result<Self, Self::Error> {
109        let recipient = JsonDeserializer::safe_to_string_from_field(value, "recipient")?;
110        let asset: Option<AssetId> = match value["assetId"].as_str() {
111            Some(value) => {
112                let vec = Base58::decode(value)?;
113                Some(AssetId::from_bytes(vec))
114            }
115            None => None,
116        };
117        let amount = JsonDeserializer::safe_to_int_from_field(value, "amount")? as u64;
118        let attachment = match value["attachment"].as_str() {
119            Some(value) => Base58String::from_string(value)?,
120            None => Base58String::empty(),
121        };
122
123        Ok(TransferTransaction {
124            recipient: Address::from_string(&recipient)?,
125            amount: Amount::new(amount, asset),
126            attachment,
127        })
128    }
129}
130
131impl TryFrom<&TransferTransaction> for Map<String, Value> {
132    type Error = Error;
133
134    fn try_from(transfer_tx: &TransferTransaction) -> Result<Self> {
135        let mut json = Map::new();
136        json.insert(
137            "recipient".to_owned(),
138            transfer_tx.recipient().encoded().into(),
139        );
140        json.insert("amount".to_owned(), transfer_tx.amount().value().into());
141        json.insert("assetId".to_owned(), transfer_tx.amount().asset_id().into());
142        json.insert(
143            "attachment".to_owned(),
144            transfer_tx.attachment().encoded().into(),
145        );
146        Ok(json)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use crate::error::Result;
153    use crate::model::{Address, Amount, AssetId, Base58String, ByteString, TransferTransaction};
154    use crate::waves_proto::recipient::Recipient;
155    use crate::waves_proto::TransferTransactionData;
156    use serde_json::{json, Map, Value};
157    use std::borrow::Borrow;
158    use std::fs;
159
160    #[test]
161    fn test_json_to_transfer_transaction() {
162        let data =
163            fs::read_to_string("./tests/resources/transfer_rs.json").expect("Unable to read file");
164        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
165
166        let transfer_tx_from_json: TransferTransaction = json.borrow().try_into().unwrap();
167
168        assert_eq!(
169            "3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q",
170            transfer_tx_from_json.recipient().encoded()
171        );
172        let amount = transfer_tx_from_json.amount();
173        assert_eq!(None, amount.asset_id());
174        assert_eq!(200000000, amount.value());
175        assert_eq!("", transfer_tx_from_json.attachment().encoded())
176    }
177
178    #[test]
179    fn test_transfer_to_proto() -> Result<()> {
180        let transfer_tx = &TransferTransaction::new(
181            Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q")?,
182            Amount::new(
183                32,
184                Some(AssetId::from_string(
185                    "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
186                )?),
187            ),
188            Base58String::from_bytes(vec![1, 2, 3]),
189        );
190        let proto: TransferTransactionData = transfer_tx.try_into()?;
191
192        let proto_recipient = if let Recipient::PublicKeyHash(bytes) =
193            proto.clone().recipient.unwrap().recipient.unwrap()
194        {
195            bytes
196        } else {
197            panic!("expected dapp public key hash")
198        };
199        assert_eq!(proto_recipient, transfer_tx.recipient().public_key_hash());
200        let amount = proto.amount.unwrap();
201        assert_eq!(amount.amount as u64, transfer_tx.amount().value());
202        assert_eq!(
203            Some(amount.asset_id),
204            transfer_tx.amount().asset_id().map(|it| it.bytes())
205        );
206
207        Ok(())
208    }
209
210    #[test]
211    fn test_transfer_to_json() -> Result<()> {
212        let transfer_tx = &TransferTransaction::new(
213            Address::from_string("3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q")?,
214            Amount::new(
215                32,
216                Some(AssetId::from_string(
217                    "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
218                )?),
219            ),
220            Base58String::from_bytes(vec![1, 2, 3]),
221        );
222        let map: Map<String, Value> = transfer_tx.try_into()?;
223        let json: Value = map.into();
224        let expected_json = json!({
225             "recipient": "3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q",
226             "assetId": "8bt2MZjuUCJPmfucPfaZPTXqrxmoCHCC8gVnbjZ7bhH6",
227             "amount": 32,
228             "attachment": "Ldp",
229        });
230        assert_eq!(expected_json, json);
231        Ok(())
232    }
233}