diff --git a/libsovtoken/src/logic/api_internals/add_request_fees.rs b/libsovtoken/src/logic/api_internals/add_request_fees.rs index 9d9c81525..79ff25c65 100644 --- a/libsovtoken/src/logic/api_internals/add_request_fees.rs +++ b/libsovtoken/src/logic/api_internals/add_request_fees.rs @@ -129,7 +129,7 @@ pub fn closure_cb_response(command_handle: i32, cb: JsonCallbackUnwrapped) -> im KEEP all public methods above */ -fn add_fees(wallet_handle: i32, inputs: Inputs, outputs: Outputs, extra: Option, request_json_map: SerdeMap, cb: Box) + Send + Sync>) -> Result<(), ErrorCode> { +fn add_fees(wallet_handle: i32, inputs: Inputs, outputs: Outputs, extra: Option, request_json_map: SerdeMap, cb: Box) + Send + Sync>) -> Result<(), ErrorCode> { let txn_serialized = serialize_signature(request_json_map.clone().into())?; let mut hasher = Sha256::default(); hasher.input(txn_serialized.as_bytes()); diff --git a/libsovtoken/src/logic/build_payment.rs b/libsovtoken/src/logic/build_payment.rs index 5a795d921..8a4453cf4 100644 --- a/libsovtoken/src/logic/build_payment.rs +++ b/libsovtoken/src/logic/build_payment.rs @@ -52,7 +52,10 @@ pub fn deserialize_inputs( debug!("Converted extra pointer to string >>> {:?}", extra); let extra: Option = if let Some(extra_) = extra { - serde_json::from_str(&extra_).map_err(map_err_err!()).or(Err(ErrorCode::CommonInvalidStructure))? + match serde_json::from_str(&extra_) { + Ok(extra_obj) => Some(extra_obj), + Err(_) => Some(Extra(serde_json::Value::String(extra_))) + } } else { None }; debug!("Deserialized extra >>> {:?}", secret!(&extra)); diff --git a/libsovtoken/src/logic/parsers/common.rs b/libsovtoken/src/logic/parsers/common.rs index 4818cf72e..5d2c94347 100644 --- a/libsovtoken/src/logic/parsers/common.rs +++ b/libsovtoken/src/logic/parsers/common.rs @@ -26,7 +26,7 @@ pub enum ResponseOperations { used by [`ParsePaymentReply`], [`ParseResponseWithFeesReply`] */ -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UTXO { pub recipient: String, diff --git a/libsovtoken/src/logic/parsers/parse_payment_response.rs b/libsovtoken/src/logic/parsers/parse_payment_response.rs index fea6edf55..e571b405e 100644 --- a/libsovtoken/src/logic/parsers/parse_payment_response.rs +++ b/libsovtoken/src/logic/parsers/parse_payment_response.rs @@ -10,12 +10,13 @@ use logic::parsers::common::{ResponseOperations, TransactionMetaData, RequireSignature}; use logic::parsers::error_code_parser; -use logic::type_aliases::{ProtocolVersion}; +use logic::type_aliases::ProtocolVersion; +use logic::xfer_payload::Extra; /** for parse_payment_response_handler input resp_json */ -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ParsePaymentResponse { pub op: ResponseOperations, @@ -27,7 +28,7 @@ pub struct ParsePaymentResponse { /** The nested type named "result in ParsePaymentResponse */ -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ParsePaymentResponseResult { pub txn: Transaction, @@ -42,7 +43,7 @@ pub struct ParsePaymentResponseResult { /** the nested type "tnx" in ParsePaymentResponseResult */ -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Transaction { #[serde(rename = "type")] @@ -56,10 +57,10 @@ pub struct Transaction { /** the nested type "data" in Transaction */ -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct TransactionData { - pub extra: Option, + pub extra: Option, pub inputs: Inputs, pub outputs: Outputs, } @@ -89,15 +90,16 @@ pub type ParsePaymentReply = Vec; pub fn from_response(base: ParsePaymentResponse) -> Result { match base.op { ResponseOperations::REPLY => { - let result = base.result.ok_or(ErrorCode::CommonInvalidStructure)?; + let result: ParsePaymentResponseResult = base.result.ok_or(ErrorCode::CommonInvalidStructure)?; + let extra = result.txn.data.extra.map(|extra|extra.to_string()).unwrap_or_default(); let mut utxos: Vec = vec![]; for unspent_output in result.txn.data.outputs { let address = unspent_output.recipient; - let amount = unspent_output.amount; + let amount = unspent_output.amount; let qualified_address: String = add_qualifer_to_address(&address); let seq_no: u64 = result.tnx_meta_data.seq_no; let txo = (TXO { address: qualified_address.to_string(), seq_no }).to_libindy_string()?; - let utxo: UTXO = UTXO { recipient: qualified_address, receipt: txo, amount, extra: "".to_string() }; + let utxo: UTXO = UTXO { recipient: qualified_address, receipt: txo, amount, extra: extra.clone() }; utxos.push(utxo); } diff --git a/libsovtoken/src/logic/parsers/parse_verify.rs b/libsovtoken/src/logic/parsers/parse_verify.rs index 064c45b34..7c6bc18e7 100644 --- a/libsovtoken/src/logic/parsers/parse_verify.rs +++ b/libsovtoken/src/logic/parsers/parse_verify.rs @@ -4,6 +4,7 @@ use logic::type_aliases::ProtocolVersion; use logic::parsers::common::ResponseOperations; use logic::output::Outputs; use logic::input::Inputs; +use logic::xfer_payload::Extra; use ErrorCode; use logic::parsers::common::UTXO; use logic::parsers::common::TXO; @@ -52,7 +53,7 @@ pub struct ParseVerifyResponseResultDataTxn { pub struct ParseVerifyResponseResultDataTxnData { pub outputs: Option, pub inputs: Option, - pub extra: Option + pub extra: Option } #[derive(Serialize, Deserialize, Debug)] @@ -84,7 +85,8 @@ fn parse_verify(resp: &str) -> Result { let mut sources: Vec = vec![]; let mut receipts: Vec = vec![]; - let extra = data.extra; + + let extra = data.extra.map(|extra|extra.to_string()); if let Some(inputs) = data.inputs { for input in inputs { @@ -100,7 +102,7 @@ fn parse_verify(resp: &str) -> Result { recipient: address.clone(), receipt: TXO { address, seq_no }.to_libindy_string()?, amount: output.amount, - extra: extra.as_ref().unwrap_or(&"".to_string()).to_string(), + extra: extra.clone().unwrap_or_default(), }) } } @@ -108,7 +110,7 @@ fn parse_verify(resp: &str) -> Result { Ok(VerifyResult { sources: Some(sources), receipts: Some(receipts), - extra, + extra: extra.clone(), }) } diff --git a/libsovtoken/src/logic/xfer_payload.rs b/libsovtoken/src/logic/xfer_payload.rs index 52011fef3..f8bd9a2b2 100644 --- a/libsovtoken/src/logic/xfer_payload.rs +++ b/libsovtoken/src/logic/xfer_payload.rs @@ -63,7 +63,18 @@ pub struct XferPayload { pub signatures: Option> } -pub type Extra = serde_json::Value; +#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)] +pub struct Extra(pub serde_json::Value); + +impl ::std::string::ToString for Extra{ + fn to_string(&self) -> String { + match self.0 { + ::serde_json::Value::Object(ref extra_obj) => json!(extra_obj).to_string(), + serde_json::Value::String(ref extra_str) => extra_str.to_string(), + _ => String::default() + } + } +} unsafe impl Send for XferPayload {} @@ -116,7 +127,7 @@ impl XferPayload { debug!("Indicator stripped from inputs"); - let (extra, taa_acceptance) = extract_taa_acceptance_from_extra(self.extra.clone())?; + let (extra, taa_acceptance) = extract_taa_acceptance_from_extra(self.extra)?; self.extra = extra; XferPayload::sign_inputs(crypto_api, wallet_handle, &self.inputs.clone(), &self.outputs.clone(), txn_digest, &self.extra.clone(), &taa_acceptance.clone(), Box::new(move |signatures| { diff --git a/libsovtoken/src/utils/txn_author_agreement.rs b/libsovtoken/src/utils/txn_author_agreement.rs index 5a80dd1cc..b5ad3ed22 100644 --- a/libsovtoken/src/utils/txn_author_agreement.rs +++ b/libsovtoken/src/utils/txn_author_agreement.rs @@ -1,15 +1,16 @@ use serde_json; use ErrorCode; +use logic::xfer_payload::Extra; pub type TaaAcceptance = serde_json::Value; const META_FIELD_NAME: &str = "taaAcceptance"; -pub fn extract_taa_acceptance_from_extra(extra: Option) -> Result<(Option, Option), ErrorCode> { +pub fn extract_taa_acceptance_from_extra(extra: Option) -> Result<(Option, Option), ErrorCode> { match extra { - Some(serde_json::Value::Object(mut extra)) => { + Some(Extra(serde_json::Value::Object(mut extra))) => { let meta = extra.remove(META_FIELD_NAME); - let extra = if extra.is_empty() { None } else { Some(json!(extra)) }; + let extra = if extra.is_empty() { None } else { Some(Extra(json!(extra))) }; Ok((extra, meta)) } Some(extra) => { @@ -31,9 +32,9 @@ mod test { "time": 123456789, }); - let extra = json!({ + let extra = Extra(json!({ "taaAcceptance": taa_acceptance.clone() - }); + })); let expected_taa = taa_acceptance.clone(); @@ -50,12 +51,12 @@ mod test { "time": 123456789, }); - let extra = json!({ + let extra = Extra(json!({ "data": "some data", "taaAcceptance": taa_acceptance.clone() - }); + })); - let expected_extra = json!({"data": "some data"}); + let expected_extra = Extra(json!({"data": "some data"})); let expected_taa = taa_acceptance.clone(); let (extra, taa_acceptance) = extract_taa_acceptance_from_extra(Some(extra)).unwrap(); @@ -65,11 +66,11 @@ mod test { #[test] pub fn extract_taa_acceptance_from_extra_works_for_no_taa_acceptance() { - let extra = json!({ + let extra = Extra(json!({ "data": "some data", - }); + })); - let expected_extra = json!({"data": "some data"}); + let expected_extra = Extra(json!({"data": "some data"})); let (extra, taa_acceptance) = extract_taa_acceptance_from_extra(Some(extra)).unwrap(); assert_eq!(expected_extra, extra.unwrap()); diff --git a/libsovtoken/tests/build_payment_req_handler_test.rs b/libsovtoken/tests/build_payment_req_handler_test.rs index 971f29775..5744827db 100644 --- a/libsovtoken/tests/build_payment_req_handler_test.rs +++ b/libsovtoken/tests/build_payment_req_handler_test.rs @@ -90,8 +90,8 @@ fn generate_payment_addresses(wallet: &Wallet) -> (Vec, Vec) { } fn get_resp_for_payment_req(pool_handle: i32, wallet_handle: i32, did: &str, - inputs: &str, outputs: &str) -> Result { - let req = build_payment_req(wallet_handle, did, inputs, outputs, None).unwrap(); + inputs: &str, outputs: &str, extra: Option) -> Result { + let req = build_payment_req(wallet_handle, did, inputs, outputs, extra).unwrap(); let res = indy::ledger::submit_request(pool_handle, &req).wait().unwrap(); parse_payment_response(&res) } @@ -316,7 +316,7 @@ pub fn build_and_submit_payment_req() { "amount": 10 } ]).to_string(); - let res = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs).unwrap(); + let res = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs, None).unwrap(); let res_parsed: serde_json::Value = serde_json::from_str(&res).unwrap(); let utxos = res_parsed.as_array().unwrap(); @@ -373,7 +373,7 @@ pub fn build_and_submit_payment_req_incorrect_funds() { } ]).to_string(); let res = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], - &inputs, &outputs_1).unwrap_err(); + &inputs, &outputs_1, None).unwrap_err(); assert_eq!(res, ErrorCode::PaymentInsufficientFundsError); let outputs_2 = json!([ @@ -387,7 +387,7 @@ pub fn build_and_submit_payment_req_incorrect_funds() { } ]).to_string(); let res = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], - &inputs, &outputs_2).unwrap_err(); + &inputs, &outputs_2, None).unwrap_err(); assert_eq!(res, ErrorCode::PaymentExtraFundsError); } @@ -415,7 +415,7 @@ pub fn build_and_submit_payment_req_with_spent_utxo() { "amount": 10 } ]).to_string(); - get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs).unwrap(); + get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs, None).unwrap(); //lets try to spend spent utxo while there are enough funds on the unspent one let inputs = json!([utxo_2, utxo]).to_string(); @@ -423,7 +423,7 @@ pub fn build_and_submit_payment_req_with_spent_utxo() { "recipient": addresses[2], "amount": 20 }]).to_string(); - let err = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs).unwrap_err(); + let err = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs, None).unwrap_err(); assert_eq!(err, ErrorCode::PaymentSourceDoesNotExistError); //utxo should stay unspent! @@ -614,4 +614,48 @@ pub fn build_payment_req_with_taa_acceptance_and_additional_extra() { assert_eq!(req_parsed["taaAcceptance"], taa_acceptance); assert_eq!(expected_operation, req_parsed["operation"]); +} + +#[test] +pub fn build_and_submit_payment_req_for_extra() { + let wallet = Wallet::new(); + let setup = Setup::new(&wallet, SetupConfig { + num_addresses: 1, + num_trustees: 4, + num_users: 0, + mint_tokens: Some(vec![20]), + fees: None, + }); + let payment_addresses = &setup.addresses; + let pool_handle = setup.pool_handle; + let dids = setup.trustees.dids(); + + for extra in ["some extra_json data as string", r#"{"data":"some extra data as string"}"#].iter() + { + let utxo = utils::payment::get_utxo::get_first_utxo_txo_for_payment_address(&wallet, pool_handle, dids[0], &payment_addresses[0]); + + let inputs = json!([utxo]).to_string(); + let outputs = json!([ + { + "recipient": payment_addresses[0], + "amount": 20 + } + ]).to_string(); + + let res = get_resp_for_payment_req(pool_handle, wallet.handle, dids[0], &inputs, &outputs, Some(extra.to_string())).unwrap(); + let res_parsed: serde_json::Value = serde_json::from_str(&res).unwrap(); + let utxos = res_parsed.as_array().unwrap(); + assert_eq!(utxos.len(), 1); + assert_eq!(*extra, utxos[0].clone()["extra"].as_str().unwrap()); + + let value = utxos.get(0).unwrap().as_object().unwrap(); + let r_1 = value.get("receipt").unwrap().as_str().unwrap(); + assert_eq!(value.get("amount").unwrap().as_i64().unwrap(), 20); + + let (req, _) = indy::payments::build_verify_payment_req(wallet.handle, None, &r_1).wait().unwrap(); + let res = indy::ledger::submit_request(pool_handle, &req).wait().unwrap(); + let res = indy::payments::parse_verify_payment_response("sov", &res).wait().unwrap(); + let res_parsed: serde_json::Value = serde_json::from_str(&res).unwrap(); + assert_eq!(*extra, res_parsed["extra"].as_str().unwrap()); + } } \ No newline at end of file