waves_rust/model/transaction/
invoke_script_transaction.rs

1use crate::error::Error::UnsupportedOperation;
2use crate::error::{Error, Result};
3use crate::model::{Address, Amount, AssetId, Base64String, ByteString, StateChanges};
4use crate::util::{ByteWriter, JsonDeserializer};
5use crate::waves_proto::InvokeScriptTransactionData;
6use crate::waves_proto::{recipient, Amount as ProtoAmount, Recipient};
7use serde_json::{Map, Number, Value};
8use std::borrow::Borrow;
9
10const TYPE: u8 = 16;
11
12#[derive(Clone, Eq, PartialEq, Debug)]
13pub struct InvokeScriptTransactionInfo {
14    dapp: Address,
15    function: Function,
16    payment: Vec<Amount>,
17    state_changes: StateChanges,
18}
19
20impl InvokeScriptTransactionInfo {
21    pub fn new(
22        dapp: Address,
23        function: Function,
24        payment: Vec<Amount>,
25        state_changes: StateChanges,
26    ) -> InvokeScriptTransactionInfo {
27        InvokeScriptTransactionInfo {
28            dapp,
29            function,
30            payment,
31            state_changes,
32        }
33    }
34
35    pub fn dapp(&self) -> Address {
36        self.dapp.clone()
37    }
38
39    pub fn function(&self) -> Function {
40        self.function.clone()
41    }
42
43    pub fn payment(&self) -> Vec<Amount> {
44        self.payment.clone()
45    }
46
47    pub fn state_changes(&self) -> StateChanges {
48        self.state_changes.clone()
49    }
50}
51
52impl TryFrom<&Value> for InvokeScriptTransactionInfo {
53    type Error = Error;
54
55    fn try_from(value: &Value) -> Result<Self> {
56        let dapp = JsonDeserializer::safe_to_string_from_field(value, "dApp")?;
57        let function: Function = value.try_into()?;
58        let payment = map_payment(value)?;
59        let state_changes = value["stateChanges"].borrow().try_into()?;
60
61        Ok(InvokeScriptTransactionInfo {
62            dapp: Address::from_string(&dapp)?,
63            function,
64            payment,
65            state_changes,
66        })
67    }
68}
69
70#[derive(Clone, Eq, PartialEq, Debug)]
71pub struct InvokeScriptTransaction {
72    dapp: Address,
73    function: Function,
74    payment: Vec<Amount>,
75}
76
77impl TryFrom<&InvokeScriptTransaction> for InvokeScriptTransactionData {
78    type Error = Error;
79
80    fn try_from(invoke_tx: &InvokeScriptTransaction) -> Result<Self> {
81        let dapp = Some(Recipient {
82            recipient: Some(recipient::Recipient::PublicKeyHash(
83                invoke_tx.dapp().public_key_hash(),
84            )),
85        });
86        let payments: Vec<ProtoAmount> = invoke_tx
87            .payment()
88            .iter()
89            .map(|amount| {
90                let asset_id = match amount.asset_id() {
91                    Some(asset) => asset.bytes(),
92                    None => vec![],
93                };
94                ProtoAmount {
95                    asset_id,
96                    amount: amount.value() as i64,
97                }
98            })
99            .collect();
100        Ok(InvokeScriptTransactionData {
101            d_app: dapp,
102            function_call: ByteWriter::bytes_from_function(&invoke_tx.function()),
103            payments,
104        })
105    }
106}
107
108#[derive(Clone, Eq, PartialEq, Debug)]
109pub struct Function {
110    name: String,
111    args: Vec<Arg>,
112}
113
114impl Function {
115    pub fn new(name: String, args: Vec<Arg>) -> Self {
116        Function { name, args }
117    }
118
119    pub fn args(&self) -> Vec<Arg> {
120        self.args.clone()
121    }
122
123    pub fn name(&self) -> String {
124        self.name.clone()
125    }
126
127    pub fn is_default(&self) -> bool {
128        self.name == "default" && self.args.is_empty()
129    }
130}
131
132impl TryFrom<&Value> for Function {
133    type Error = Error;
134
135    fn try_from(value: &Value) -> Result<Self> {
136        let call = JsonDeserializer::safe_to_map_from_field(value, "call")?;
137        let function_name = match call.get("function") {
138            Some(func_name) => JsonDeserializer::safe_to_string(func_name)?,
139            None => "".to_owned(),
140        };
141        let args = match call.get("args") {
142            Some(args) => map_args(args)?,
143            None => vec![],
144        };
145
146        Ok(Function {
147            name: function_name,
148            args,
149        })
150    }
151}
152
153impl InvokeScriptTransaction {
154    pub fn from_json(value: &Value) -> Result<InvokeScriptTransaction> {
155        let dapp =
156            Address::from_string(&JsonDeserializer::safe_to_string_from_field(value, "dApp")?)?;
157        let function: Function = value.try_into()?;
158        let payments = map_payment(value)?;
159
160        Ok(InvokeScriptTransaction {
161            dapp,
162            function,
163            payment: payments,
164        })
165    }
166
167    pub fn new(dapp: Address, function: Function, payment: Vec<Amount>) -> Self {
168        InvokeScriptTransaction {
169            dapp,
170            function,
171            payment,
172        }
173    }
174
175    pub fn tx_type() -> u8 {
176        TYPE
177    }
178
179    pub fn dapp(&self) -> Address {
180        self.dapp.clone()
181    }
182
183    pub fn function(&self) -> Function {
184        self.function.clone()
185    }
186
187    pub fn payment(&self) -> Vec<Amount> {
188        self.payment.clone()
189    }
190}
191
192#[derive(Clone, Eq, PartialEq, Debug)]
193pub enum Arg {
194    Binary(Base64String),
195    Boolean(bool),
196    Integer(i64),
197    String(String),
198    List(Vec<Arg>),
199}
200
201fn map_payment(value: &Value) -> Result<Vec<Amount>> {
202    JsonDeserializer::safe_to_array_from_field(value, "payment")?
203        .iter()
204        .map(|payment| {
205            let value = JsonDeserializer::safe_to_int_from_field(payment, "amount")?;
206            let asset_id = match payment["assetId"].as_str() {
207                Some(asset) => Some(AssetId::from_string(asset)?),
208                None => None,
209            };
210            Ok(Amount::new(value as u64, asset_id))
211        })
212        .collect::<Result<Vec<Amount>>>()
213}
214
215fn map_args(value: &Value) -> Result<Vec<Arg>> {
216    let mut args: Vec<Arg> = vec![];
217    for arg in JsonDeserializer::safe_to_array(value)? {
218        let arg = match JsonDeserializer::safe_to_string_from_field(&arg, "type")?.as_str() {
219            "boolean" | "Boolean" => {
220                Arg::Boolean(JsonDeserializer::safe_to_boolean_from_field(&arg, "value")?)
221            }
222            "string" | "String" => {
223                Arg::String(JsonDeserializer::safe_to_string_from_field(&arg, "value")?)
224            }
225            "integer" | "Int" => {
226                Arg::Integer(JsonDeserializer::safe_to_int_from_field(&arg, "value")?)
227            }
228            "binary" | "ByteVector" => Arg::Binary(Base64String::from_string(
229                &JsonDeserializer::safe_to_string_from_field(&arg, "value")?,
230            )?),
231            "list" | "List" | "Array" => {
232                let result = map_args(&arg["value"])?;
233                Arg::List(result)
234            }
235            _ => return Err(UnsupportedOperation("unknown type".to_owned())),
236        };
237        args.push(arg);
238    }
239    Ok(args)
240}
241
242impl TryFrom<&InvokeScriptTransaction> for Map<String, Value> {
243    type Error = Error;
244
245    fn try_from(invoke_tx: &InvokeScriptTransaction) -> Result<Self> {
246        let mut json = Map::new();
247        json.insert("dApp".to_owned(), invoke_tx.dapp().encoded().into());
248        let mut call: Map<String, Value> = Map::new();
249        call.insert("function".to_owned(), invoke_tx.function().name().into());
250        let args = invoke_tx
251            .function()
252            .args()
253            .iter()
254            .map(|arg| arg.try_into())
255            .collect::<Result<Vec<Value>>>()?;
256        call.insert("args".to_owned(), Value::Array(args));
257        json.insert("call".to_owned(), call.into());
258        let payments: Vec<Value> = invoke_tx
259            .payment()
260            .iter()
261            .map(|arg| {
262                let mut map = Map::new();
263                map.insert("amount".to_owned(), arg.value().into());
264                map.insert(
265                    "assetId".to_owned(),
266                    arg.asset_id().map(|it| it.encoded()).into(),
267                );
268                map.into()
269            })
270            .collect();
271        json.insert("payment".to_owned(), payments.into());
272        Ok(json)
273    }
274}
275
276impl TryFrom<&Arg> for Value {
277    type Error = Error;
278
279    fn try_from(arg: &Arg) -> Result<Self> {
280        let mut arg_map = Map::new();
281        match arg {
282            Arg::Binary(binary) => {
283                arg_map.insert("type".to_owned(), "binary".into());
284                arg_map.insert("value".to_owned(), binary.encoded_with_prefix().into());
285            }
286            Arg::Boolean(boolean) => {
287                arg_map.insert("type".to_owned(), "boolean".into());
288                arg_map.insert("value".to_owned(), Value::Bool(*boolean));
289            }
290            Arg::Integer(integer) => {
291                arg_map.insert("type".to_owned(), "integer".into());
292                arg_map.insert("value".to_owned(), Value::Number(Number::from(*integer)));
293            }
294            Arg::String(string) => {
295                arg_map.insert("type".to_owned(), "string".into());
296                arg_map.insert("value".to_owned(), Value::String(string.to_owned()));
297            }
298            Arg::List(list) => {
299                arg_map.insert("type".to_owned(), "list".into());
300                let list_args = list
301                    .iter()
302                    .map(|arg| arg.try_into())
303                    .collect::<Result<Vec<Value>>>()?;
304                arg_map.insert("value".to_owned(), Value::Array(list_args));
305            }
306        };
307        Ok(arg_map.into())
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use crate::error::Result;
314    use crate::model::data_entry::DataEntry;
315    use crate::model::{
316        Address, Amount, Arg, AssetId, Base64String, ByteString, Function, InvokeScriptTransaction,
317        InvokeScriptTransactionInfo, LeaseStatus,
318    };
319    use crate::util::ByteWriter;
320    use crate::waves_proto::recipient::Recipient;
321    use crate::waves_proto::InvokeScriptTransactionData;
322    use serde_json::{json, Map, Value};
323    use std::borrow::Borrow;
324    use std::fs;
325
326    #[test]
327    fn test_json_to_invoke_script_transaction() -> Result<()> {
328        let data = fs::read_to_string("./tests/resources/invoke_script_rs.json")
329            .expect("Unable to read file");
330        let json: Value = serde_json::from_str(&data).expect("failed to generate json from str");
331
332        let invoke_script_from_json: InvokeScriptTransactionInfo =
333            json.borrow().try_into().unwrap();
334        let function = invoke_script_from_json.function();
335        assert_eq!("checkPointAndPoligon", function.name());
336        assert_eq!(
337            true,
338            match &function.args()[0] {
339                Arg::Boolean(value) => *value,
340                _ => panic!("wrong type"),
341            }
342        );
343        assert_eq!(
344            "some string",
345            match &function.args()[1] {
346                Arg::String(value) => value,
347                _ => panic!("wrong type"),
348            }
349        );
350        assert_eq!(
351            123,
352            match &function.args()[2] {
353                Arg::Integer(value) => *value,
354                _ => panic!("wrong type"),
355            }
356        );
357        assert_eq!(
358            "base64:AwUCCw8=",
359            match &function.args()[3] {
360                Arg::Binary(value) => value.encoded_with_prefix(),
361                _ => panic!("wrong type"),
362            }
363        );
364
365        match &function.args()[4] {
366            Arg::List(value) => {
367                match value[0] {
368                    Arg::Integer(int) => {
369                        assert_eq!(int, 123)
370                    }
371                    _ => panic!("wrong type"),
372                }
373                match value[1] {
374                    Arg::Integer(int) => {
375                        assert_eq!(int, 543)
376                    }
377                    _ => panic!("wrong type"),
378                }
379            }
380            _ => panic!("wrong type"),
381        }
382
383        let payments = invoke_script_from_json.payment();
384        assert_eq!(2, payments.len());
385
386        let payment1 = &payments[0];
387        assert_eq!(payment1.asset_id(), None);
388        assert_eq!(payment1.value(), 1);
389        let payment2 = &payments[1];
390        assert_eq!(
391            payment2.asset_id(),
392            Some(AssetId::from_string(
393                "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ"
394            )?)
395        );
396        assert_eq!(payment2.value(), 2);
397
398        let state_changes = invoke_script_from_json.state_changes();
399        let data_entries = state_changes.data();
400        assert_eq!(5, data_entries.len());
401        for data_entry in data_entries {
402            match data_entry {
403                DataEntry::IntegerEntry { key, value } => {
404                    assert_eq!("int", key);
405                    assert_eq!(2514, value);
406                }
407                DataEntry::BooleanEntry { key, value } => {
408                    assert_eq!("bool", key);
409                    assert_eq!(true, value)
410                }
411                DataEntry::BinaryEntry { key, value } => {
412                    assert_eq!("bin", key);
413                    assert_eq!("mmXJ", Base64String::from_bytes(value).encoded());
414                }
415                DataEntry::StringEntry { key, value } => {
416                    assert_eq!("str", key);
417                    assert_eq!("", value)
418                }
419                DataEntry::DeleteEntry { key } => {
420                    assert_eq!("str", key)
421                }
422            }
423        }
424
425        let transfers = state_changes.transfers();
426        assert_eq!(1, transfers.len());
427        let transfer = &transfers[0];
428        assert_eq!(
429            "3MQ833eGnNM5dtRWGBaKFpmRfxfrnmeKd9G",
430            transfer.recipient().encoded()
431        );
432        assert_eq!(
433            "AuEwc87bodoeofX5pdbt9ebU7K5zrz85frwDwoFeuQoa",
434            transfer.amount().asset_id().expect("failed").encoded()
435        );
436        assert_eq!(1, transfer.amount().value());
437
438        let issues = state_changes.issues();
439        assert_eq!(1, issues.len());
440        let issue = &issues[0];
441        assert_eq!(
442            "AuEwc87bodoeofX5pdbt9ebU7K5zrz85frwDwoFeuQoa",
443            issue.asset_id().encoded()
444        );
445        assert_eq!("Asset", issue.name());
446        assert_eq!("", issue.description());
447        assert_eq!(1, issue.quantity());
448        assert_eq!(0, issue.decimals());
449        assert_eq!(true, issue.is_reissuable());
450        assert_eq!("", issue.script().encoded());
451        assert_eq!(0, issue.nonce());
452
453        let reissues = state_changes.reissues();
454        assert_eq!(1, reissues.len());
455        let reissue = &reissues[0];
456        assert_eq!(
457            "AuEwc87bodoeofX5pdbt9ebU7K5zrz85frwDwoFeuQoa",
458            reissue.asset_id().encoded()
459        );
460        assert_eq!(false, reissue.is_reissuable());
461        assert_eq!(1, reissue.quantity());
462
463        let burns = state_changes.burns();
464        assert_eq!(1, burns.len());
465        let burn = &burns[0];
466        assert_eq!(
467            "AuEwc87bodoeofX5pdbt9ebU7K5zrz85frwDwoFeuQoa",
468            burn.asset_id().encoded()
469        );
470        assert_eq!(1, burn.amount());
471
472        let sponsor_fees = state_changes.sponsor_fees();
473        assert_eq!(1, sponsor_fees.len());
474        let sponsor_fee = &sponsor_fees[0];
475        assert_eq!(
476            "GyH2wqKQcjHtz6KgkUNzUpDYYy1azqZdYHZ2awXHWqYx",
477            sponsor_fee.asset_id().encoded()
478        );
479        assert_eq!(1, sponsor_fee.min_sponsored_asset_fee());
480
481        let leases = state_changes.leases();
482        assert_eq!(1, leases.len());
483        let lease_info = &leases[0];
484        assert_eq!(
485            "9zzpWBv63hh91FDdBnaeTDRVhgvqE4vdnwtYkGU9SvNb",
486            lease_info.id().encoded()
487        );
488        assert_eq!(
489            "4XFVLLMBjBMPwGivgyLhw374kViANoToLAYUdEXWLsBJ",
490            lease_info.origin_transaction_id().encoded()
491        );
492        assert_eq!(
493            "3MwjNKQ9aAoAdBKGAR9cmsq8sRicQVitGVz",
494            lease_info.sender().encoded()
495        );
496        assert_eq!(
497            "3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q",
498            lease_info.recipient().encoded()
499        );
500        assert_eq!(7, lease_info.amount());
501        assert_eq!(2217333, lease_info.height());
502        assert_eq!(LeaseStatus::Canceled, lease_info.status());
503        assert_eq!(Some(2217333), lease_info.cancel_height());
504        assert_eq!(
505            Some("4XFVLLMBjBMPwGivgyLhw374kViANoToLAYUdEXWLsBJ".to_owned()),
506            lease_info.cancel_transaction_id().map(|it| it.encoded())
507        );
508
509        let lease_cancels = state_changes.lease_cancels();
510        assert_eq!(1, lease_cancels.len());
511        let lease_cancel_info = &lease_cancels[0];
512        assert_eq!(
513            "9zzpWBv63hh91FDdBnaeTDRVhgvqE4vdnwtYkGU9SvNb",
514            lease_cancel_info.id().encoded()
515        );
516        assert_eq!(
517            "4XFVLLMBjBMPwGivgyLhw374kViANoToLAYUdEXWLsBJ",
518            lease_cancel_info.origin_transaction_id().encoded()
519        );
520        assert_eq!(
521            "3MwjNKQ9aAoAdBKGAR9cmsq8sRicQVitGVz",
522            lease_cancel_info.sender().encoded()
523        );
524        assert_eq!(
525            "3Mq3pueXcAgLcuWvJzJ4ndRHfqYgjUZvL7q",
526            lease_cancel_info.recipient().encoded()
527        );
528        assert_eq!(7, lease_cancel_info.amount());
529        assert_eq!(2217333, lease_cancel_info.height());
530        assert_eq!(LeaseStatus::Canceled, lease_cancel_info.status());
531        assert_eq!(Some(2217333), lease_cancel_info.cancel_height());
532        assert_eq!(
533            Some("4XFVLLMBjBMPwGivgyLhw374kViANoToLAYUdEXWLsBJ".to_owned()),
534            lease_cancel_info
535                .cancel_transaction_id()
536                .map(|it| it.encoded())
537        );
538
539        let invokes = state_changes.invokes();
540        assert_eq!(2, invokes.len());
541        let first_invoke = &invokes[0];
542        assert_eq!(
543            "3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K",
544            first_invoke.dapp().encoded()
545        );
546        assert_eq!("selfCall", first_invoke.function().name());
547        assert_eq!(1, first_invoke.function().args().len());
548        let inner_invoke = &first_invoke.state_changes().invokes()[0];
549        assert_eq!(
550            "3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K",
551            inner_invoke.dapp().encoded()
552        );
553        assert_eq!("selfCall2", inner_invoke.function().name());
554        assert_eq!(1, inner_invoke.function().args().len());
555        let second_invoke = &invokes[1];
556        assert_eq!(
557            "3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K",
558            second_invoke.dapp().encoded()
559        );
560        assert_eq!("selfCall1", second_invoke.function().name());
561        assert_eq!(1, second_invoke.function().args().len());
562        Ok(())
563    }
564
565    #[test]
566    fn test_invoke_script_transaction_to_proto() -> Result<()> {
567        let invoke_script = &InvokeScriptTransaction::new(
568            Address::from_string("3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K")?,
569            Function::new("function".to_owned(), vec![Arg::String("123".to_owned())]),
570            vec![Amount::new(
571                1,
572                Some(AssetId::from_string(
573                    "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ",
574                )?),
575            )],
576        );
577
578        let proto: InvokeScriptTransactionData = invoke_script.try_into()?;
579
580        assert_eq!(
581            proto.function_call,
582            ByteWriter::bytes_from_function(&invoke_script.function())
583        );
584        let proto_d_app = if let Recipient::PublicKeyHash(bytes) =
585            proto.clone().d_app.unwrap().recipient.unwrap()
586        {
587            bytes
588        } else {
589            panic!("expected dapp public key hash")
590        };
591        assert_eq!(proto_d_app, invoke_script.dapp().public_key_hash());
592
593        assert_eq!(
594            proto.payments[0].amount as u64,
595            invoke_script.payment()[0].value()
596        );
597        assert_eq!(
598            &proto.payments[0].asset_id,
599            &invoke_script.payment()[0].asset_id().unwrap().bytes()
600        );
601
602        Ok(())
603    }
604
605    #[test]
606    fn test_invoke_script_transaction_to_json() -> Result<()> {
607        let expected_json = json!({
608            "dApp": "3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K",
609            "payment": [
610              {
611                "amount": 1,
612                "assetId": null
613              },
614              {
615                "amount": 2,
616                "assetId": "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ"
617              }
618            ],
619            "call": {
620              "function": "checkPointAndPoligon",
621              "args": [
622                {
623                  "type": "boolean",
624                  "value": true
625                },
626                {
627                  "type": "string",
628                  "value": "some string"
629                },
630                {
631                  "type": "integer",
632                  "value": 123
633                },
634                {
635                  "type": "binary",
636                  "value": "base64:AwUCCw8="
637                },
638                {
639                  "type": "list",
640                  "value": [
641                    {
642                      "type": "integer",
643                      "value": 123
644                    },
645                    {
646                      "type": "integer",
647                      "value": 543
648                    }
649                  ]
650                }
651              ]
652            }
653        });
654
655        let function = Function::new(
656            "checkPointAndPoligon".to_owned(),
657            vec![
658                Arg::Boolean(true),
659                Arg::String("some string".to_owned()),
660                Arg::Integer(123),
661                Arg::Binary(Base64String::from_string("AwUCCw8=")?),
662                Arg::List(vec![Arg::Integer(123), Arg::Integer(543)]),
663            ],
664        );
665        let invoke_script = &InvokeScriptTransaction::new(
666            Address::from_string("3MFTz4aKdjAMcvFUYFdDv7jPiKtpeUv9r3K")?,
667            function,
668            vec![
669                Amount::new(1, None),
670                Amount::new(
671                    2,
672                    Some(AssetId::from_string(
673                        "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ",
674                    )?),
675                ),
676            ],
677        );
678
679        let map: Map<String, Value> = invoke_script.try_into()?;
680        let json: Value = map.into();
681
682        assert_eq!(expected_json, json);
683
684        Ok(())
685    }
686}