Repository: ergoplatform/sigma-rust (language: rust)

Issue:
JSON encoding for UnsignedTransaction for /wallet/transaction/sign endpoint



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/src/chain/input.rs b/ergo-lib/src/chain/input.rs
index 42807dd24..b9df9df56 100644
--- a/ergo-lib/src/chain/input.rs
+++ b/ergo-lib/src/chain/input.rs
@@ -17,10 +17,13 @@ use serde::{Deserialize, Serialize};
 /// Unsigned (without proofs) transaction input
 #[derive(PartialEq, Debug, Clone)]
 #[cfg_attr(test, derive(proptest_derive::Arbitrary))]
+#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 pub struct UnsignedInput {
     /// id of the box to spent
+    #[cfg_attr(feature = "json", serde(rename = "boxId"))]
     pub box_id: BoxId,
     /// user-defined variables to be put into context
+    #[cfg_attr(feature = "json", serde(rename = "extension"))]
     pub extension: ContextExtension,
 }
 
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index 2372a6abf..d5bdab9b6 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -79,6 +79,7 @@ pub mod ergo_box {
 }
 
 pub mod transaction {
+    use crate::chain::input::UnsignedInput;
     use crate::chain::{data_input::DataInput, ergo_box::ErgoBox, input::Input, transaction::TxId};
     use serde::{Deserialize, Serialize};
 
@@ -97,10 +98,29 @@ pub mod transaction {
         #[cfg_attr(feature = "json", serde(rename = "outputs"))]
         pub outputs: Vec<ErgoBox>,
     }
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    pub struct UnsignedTransactionJson {
+        #[cfg_attr(feature = "json", serde(rename = "id"))]
+        pub tx_id: TxId,
+        /// inputs, that will be spent by this transaction.
+        #[cfg_attr(feature = "json", serde(rename = "inputs"))]
+        pub inputs: Vec<UnsignedInput>,
+        /// inputs, that are not going to be spent by transaction, but will be reachable from inputs
+        /// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
+        /// included in transaction cost and they do not contain spending proofs.
+        #[cfg_attr(feature = "json", serde(rename = "dataInputs"))]
+        pub data_inputs: Vec<DataInput>,
+        // we're using ErgoBox here instead of ErgoBoxCandidate,
+        // since ErgoBox is expected here in the node REST API
+        #[cfg_attr(feature = "json", serde(rename = "outputs"))]
+        pub outputs: Vec<ErgoBox>,
+    }
 }
 
 #[cfg(test)]
 mod tests {
+    use crate::chain::transaction::unsigned::UnsignedTransaction;
     use std::convert::TryInto;
 
     use super::super::ergo_box::*;
@@ -129,6 +149,15 @@ mod tests {
             prop_assert_eq![t, t_parsed];
         }
 
+        #[test]
+        fn unsigned_tx_roundtrip(t in any::<UnsignedTransaction>()) {
+            let j = serde_json::to_string(&t)?;
+            // dbg!(j);
+            eprintln!("{}", j);
+            let t_parsed: UnsignedTransaction = serde_json::from_str(&j)?;
+            prop_assert_eq![t, t_parsed];
+        }
+
     }
 
     #[test]
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index 615b3e6a8..f6bdf5942 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -1,11 +1,17 @@
 //! Unsigned (without proofs) transaction
 
+#[cfg(feature = "json")]
+use super::json;
 use super::{
     super::{
         data_input::DataInput, digest32::blake2b256_hash, ergo_box::ErgoBoxCandidate, input::Input,
     },
     Transaction, TxId,
 };
+#[cfg(feature = "json")]
+use crate::chain::transaction::ErgoBox;
+#[cfg(feature = "json")]
+use crate::chain::transaction::TransactionFromJsonError;
 use crate::{
     chain::{
         input::UnsignedInput,
@@ -13,8 +19,20 @@ use crate::{
     },
     serialization::SigmaSerializable,
 };
+#[cfg(feature = "json")]
+use core::convert::TryFrom;
+#[cfg(feature = "json")]
+use serde::{Deserialize, Serialize};
 
 /// Unsigned (inputs without proofs) transaction
+#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
+#[cfg_attr(
+    feature = "json",
+    serde(
+        try_from = "json::transaction::UnsignedTransactionJson",
+        into = "json::transaction::UnsignedTransactionJson"
+    )
+)]
 #[derive(PartialEq, Debug, Clone)]
 pub struct UnsignedTransaction {
     tx_id: TxId,
@@ -75,6 +93,49 @@ impl UnsignedTransaction {
     }
 }
 
+#[cfg(feature = "json")]
+impl TryFrom<json::transaction::UnsignedTransactionJson> for UnsignedTransaction {
+    type Error = TransactionFromJsonError;
+    fn try_from(
+        tx_json: json::transaction::UnsignedTransactionJson,
+    ) -> std::result::Result<Self, Self::Error> {
+        let output_candidates = tx_json.outputs.iter().map(|o| o.clone().into()).collect();
+        let tx_to_sign = UnsignedTransaction {
+            tx_id: TxId::zero(),
+            inputs: tx_json.inputs,
+            data_inputs: tx_json.data_inputs,
+            output_candidates,
+        };
+        let tx_id = tx_to_sign.calc_tx_id();
+        let tx = UnsignedTransaction {
+            tx_id,
+            ..tx_to_sign
+        };
+        if tx.tx_id == tx_json.tx_id {
+            Ok(tx)
+        } else {
+            Err(TransactionFromJsonError::InvalidTxId)
+        }
+    }
+}
+
+#[cfg(feature = "json")]
+impl From<UnsignedTransaction> for json::transaction::UnsignedTransactionJson {
+    fn from(t: UnsignedTransaction) -> Self {
+        json::transaction::UnsignedTransactionJson {
+            tx_id: t.tx_id.clone(),
+            inputs: t.inputs.clone(),
+            data_inputs: t.data_inputs.clone(),
+            outputs: t
+                .output_candidates
+                .iter()
+                .enumerate()
+                .map(|(idx, c)| ErgoBox::from_box_candidate(c, t.tx_id.clone(), idx as u16))
+                .collect(),
+        }
+    }
+}
+
 #[cfg(test)]
 pub mod tests {
     use super::*;


Test patch: