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}