From 9a094be7fab87afaa24b4c8e58a8d11b00dfe146 Mon Sep 17 00:00:00 2001 From: matiasbzurovski <164921079+matiasbzurovski@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:53:56 +0100 Subject: [PATCH] [ABW-4003] Update WalletToDappInteractionSubintentResponseItem (#279) * updates to models * keeping SignedSubinent * version bump * add test * add test and fix * changes from feedback * updates to meet CAP updates * send Timestamp when polling PreAuth status * fix test --- Cargo.lock | 4 +- crates/sargon-uniffi/Cargo.toml | 2 +- .../pre_authorization/pre_authorization.rs | 9 +- .../sargon_os_pre_authorization_status.rs | 4 +- crates/sargon/Cargo.toml | 2 +- .../wallet_interactions_wallet_to_dapp.json | 4 +- crates/sargon/src/core/assert_json.rs | 9 + .../pre_authorization/pre_authorization.rs | 9 +- .../pre_authorization/subintent_response.rs | 161 +++++++++++++++--- .../sargon_os_pre_authorization_status.rs | 136 +++++---------- .../low_level/v2/signed_subintent.rs | 46 +++++ crates/sargon/tests/vectors/main.rs | 18 +- 12 files changed, 272 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5749aedbf..1d9a5028a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.64" +version = "1.1.65" dependencies = [ "actix-rt", "aes-gcm", @@ -2814,7 +2814,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.64" +version = "1.1.65" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index 3831be461..220e4937d 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.64" +version = "1.1.65" edition = "2021" build = "build.rs" diff --git a/crates/sargon-uniffi/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs b/crates/sargon-uniffi/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs index 24a296467..4854301b5 100644 --- a/crates/sargon-uniffi/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs +++ b/crates/sargon-uniffi/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs @@ -9,16 +9,21 @@ pub struct WalletToDappInteractionPreAuthorizationResponseItems { #[derive(Clone, PartialEq, InternalConversion, uniffi::Record)] pub struct WalletToDappInteractionSubintentResponseItem { - /// A hex encoded signed partial transaction. - pub encoded_signed_partial_transaction: String, + /// A signed subintent + pub signed_subintent: SignedSubintent, + + /// The timestamp at which the subintent expires + pub expiration_timestamp: Timestamp, } #[uniffi::export] pub fn new_wallet_to_dapp_interaction_pre_authorization_response_items( signed_subintent: SignedSubintent, + expiration_timestamp: Timestamp, ) -> WalletToDappInteractionPreAuthorizationResponseItems { InternalWalletToDappInteractionPreAuthorizationResponseItems::new( signed_subintent.into(), + expiration_timestamp, ) .into() } diff --git a/crates/sargon-uniffi/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs b/crates/sargon-uniffi/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs index f8d336b17..4c4902f42 100644 --- a/crates/sargon-uniffi/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs +++ b/crates/sargon-uniffi/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs @@ -9,12 +9,12 @@ impl SargonOS { pub async fn poll_pre_authorization_status( &self, intent_hash: SubintentHash, - expiration: DappToWalletInteractionSubintentExpiration, + expiration_timestamp: Timestamp, ) -> Result { self.wrapped .poll_pre_authorization_status( intent_hash.into_internal(), - expiration.into_internal(), + expiration_timestamp, ) .await .into_result() diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index 382b0fc6f..43069a937 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.64" +version = "1.1.65" edition = "2021" build = "build.rs" diff --git a/crates/sargon/fixtures/vector/wallet_interactions_wallet_to_dapp.json b/crates/sargon/fixtures/vector/wallet_interactions_wallet_to_dapp.json index 9c944d724..bdecef58d 100644 --- a/crates/sargon/fixtures/vector/wallet_interactions_wallet_to_dapp.json +++ b/crates/sargon/fixtures/vector/wallet_interactions_wallet_to_dapp.json @@ -180,7 +180,9 @@ "items": { "discriminator": "preAuthorizationResponse", "response" : { - "signedPartialTransaction": "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202201000121012007410001598e989470d125dafac276b95bb1ba21e2ee8e0beb0547599335f83b48a0a830cd6a956a54421039cef5fb7e492ebaa315f751a2dd5b74bd9cebbda997ec12202000" + "signedPartialTransaction": "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202200202000", + "subintentHash": "subtxid_sim1kdwxe9mkpgn2n5zplvh4kcu0d69k5qcz679xhxfa8ulcjtjqsvtq799xkn", + "expirationTimestamp": 1694448356 } } } diff --git a/crates/sargon/src/core/assert_json.rs b/crates/sargon/src/core/assert_json.rs index f511a57eb..fc49f9dc8 100644 --- a/crates/sargon/src/core/assert_json.rs +++ b/crates/sargon/src/core/assert_json.rs @@ -172,3 +172,12 @@ where let json = json_string.parse::().unwrap(); assert_json_value_fails::(json) } + +#[cfg(not(tarpaulin_include))] +pub fn assert_json_eq_ignore_whitespace(json1: &str, json2: &str) { + let value1: Value = + serde_json::from_str(json1).expect("Invalid JSON in json1"); + let value2: Value = + serde_json::from_str(json2).expect("Invalid JSON in json2"); + assert_eq!(value1, value2, "JSON strings do not match"); +} diff --git a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs index 19edb1c6f..7877896c7 100644 --- a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs +++ b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/pre_authorization.rs @@ -7,10 +7,14 @@ pub struct WalletToDappInteractionPreAuthorizationResponseItems { } impl WalletToDappInteractionPreAuthorizationResponseItems { - pub fn new(signed_subintent: SignedSubintent) -> Self { + pub fn new( + signed_subintent: SignedSubintent, + expiration_timestamp: Timestamp, + ) -> Self { Self { response: WalletToDappInteractionSubintentResponseItem::new( signed_subintent, + expiration_timestamp, ), } } @@ -51,8 +55,7 @@ mod tests { #[test] fn new() { - let signed_subintent = SignedSubintent::sample(); - let sut = SUT::new(signed_subintent); + let sut = SUT::new(SignedSubintent::sample(), Timestamp::sample()); assert_eq!(sut, SUT::sample()); } } diff --git a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/subintent_response.rs b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/subintent_response.rs index 87a931817..2bfa56d7b 100644 --- a/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/subintent_response.rs +++ b/crates/sargon/src/radix_connect/wallet_interaction/dapp_wallet_interaction/wallet_to_dapp/success_response/pre_authorization/subintent_response.rs @@ -1,35 +1,98 @@ use crate::prelude::*; +use std::time::Duration; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct WalletToDappInteractionSubintentResponseItem { - /// A hex encoded signed partial transaction. - #[serde(rename = "signedPartialTransaction")] - pub encoded_signed_partial_transaction: String, + /// A signed subintent + pub signed_subintent: SignedSubintent, + + /// The timestamp at which the subintent expires + pub expiration_timestamp: Timestamp, } impl WalletToDappInteractionSubintentResponseItem { - pub fn new(signed_subintent: SignedSubintent) -> Self { - let bytes = signed_subintent.compiled(); - let encoded_signed_partial_transaction = hex_encode(&bytes); + pub fn new( + signed_subintent: SignedSubintent, + expiration_timestamp: Timestamp, + ) -> Self { Self { - encoded_signed_partial_transaction, + signed_subintent, + expiration_timestamp, + } + } + + fn encoded_signed_partial_transaction(&self) -> String { + let bytes = self.signed_subintent.compiled(); + hex_encode(&bytes) + } + + fn subintent_hash(&self) -> SubintentHash { + self.signed_subintent.subintent.hash() + } + + fn expiration_timestamp_seconds(&self) -> u64 { + self.expiration_timestamp + .duration_since(Timestamp::UNIX_EPOCH) + .as_seconds_f64() as u64 + } +} + +impl Serialize for WalletToDappInteractionSubintentResponseItem { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct( + "WalletToDappInteractionSubintentResponseItem", + 3, + )?; + state.serialize_field( + "signedPartialTransaction", + &self.encoded_signed_partial_transaction(), + )?; + state.serialize_field( + "subintentHash", + &self.subintent_hash().to_string(), + )?; + state.serialize_field( + "expirationTimestamp", + &self.expiration_timestamp_seconds(), + )?; + state.end() + } +} + +impl<'de> Deserialize<'de> for WalletToDappInteractionSubintentResponseItem { + #[cfg(not(tarpaulin_include))] // false negative + fn deserialize>( + deserializer: D, + ) -> Result { + #[derive(Deserialize, Serialize)] + struct Wrapper { + #[serde(rename = "signedPartialTransaction")] + encoded_signed_partial_transaction: String, + + #[serde(rename = "expirationTimestamp")] + expiration_timestamp_seconds: u64, } + let wrapped = Wrapper::deserialize(deserializer)?; + let decoded = hex_decode(wrapped.encoded_signed_partial_transaction) + .map_err(de::Error::custom)?; + let expiration_timestamp = Timestamp::UNIX_EPOCH + .add(Duration::from_secs(wrapped.expiration_timestamp_seconds)); + SignedSubintent::decompiling(decoded) + .map_err(de::Error::custom) + .map(|s| Self::new(s, expiration_timestamp)) } } impl HasSampleValues for WalletToDappInteractionSubintentResponseItem { fn sample() -> Self { - Self { - encoded_signed_partial_transaction: - "4d220e03210221012105210607010a872c0100000000000a912c01000000000022010105008306670000000022010105e8860667000000000a15cd5b070000000020200022010121020c0a746578742f706c61696e2200010c0c48656c6c6f205261646978212020002022054103800051c9a978fb5bfa066a3e5658251ee3304fb9bf58c35b61f8c10e0e7b91840c086c6f636b5f6665652101850000fda0c4277708000000000000000000000000000000004103800051c9a978fb5bfa066a3e5658251ee3304fb9bf58c35b61f8c10e0e7b91840c087769746864726177210280005da66318c6318c61f5a61b4c6318c6318cf794aa8d295f14e6318c6318c6850000443945309a7a48000000000000000000000000000000000280005da66318c6318c61f5a61b4c6318c6318cf794aa8d295f14e6318c6318c6850000443945309a7a480000000000000000000000000000004103800051ac224ee242c339b5ea5f1ae567f0520a6ffa24b52a10b8e6cd96a8347f0c147472795f6465706f7369745f6f725f61626f727421028100000000220000600121002021002022030001210120074101069ff73da4b2c861c340558d0d7ee44bfb8f221f4ba7f8d74a9e9d82c1acd2f951afd718faddb24a11062a508ad6edf31c519f88973688eaf04fc0cd944bebdc00012101200741007a324555c61268211c4dae7c63a5f94f3be523d7b0b93426685c8767d74907fb348775142bb6e1ac6d236acf795b672b7c94114e4198caec995d86d1327d5c060001210120074100fb65235bc47969a6b4421b8495641e9bec403103df5fa4ed7a123df0dc97f1734822bc9e609e00aa13698ba3227a61a8aff23fcc0f188eed9f29da155aa5c894202000".to_owned(), - } + Self::new(SignedSubintent::sample(), Timestamp::sample()) } fn sample_other() -> Self { - Self { - encoded_signed_partial_transaction: - "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202201000121012007410001598e989470d125dafac276b95bb1ba21e2ee8e0beb0547599335f83b48a0a830cd6a956a54421039cef5fb7e492ebaa315f751a2dd5b74bd9cebbda997ec12202000".to_owned(), - } + Self::new(SignedSubintent::sample_other(), Timestamp::sample_other()) } } @@ -52,9 +115,67 @@ mod tests { } #[test] - fn new() { - let signed_subintent = SignedSubintent::sample(); - let sut = SUT::new(signed_subintent); - assert_eq!(sut, SUT::sample()); + fn json_success() { + // Test roundtrip for sample (whose timestamp doesn't include milliseconds). + assert_json_roundtrip(&SUT::sample()); + + // Test encoding of sample_other (whose timestamp includes 123 milliseconds). + let result = + serde_json::to_string_pretty(&SUT::sample_other()).unwrap(); + let json = r#" + { + "signedPartialTransaction": "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202200202000", + "subintentHash": "subtxid_sim1kdwxe9mkpgn2n5zplvh4kcu0d69k5qcz679xhxfa8ulcjtjqsvtq799xkn", + "expirationTimestamp": 1703438036 + } + "#; + assert_json_eq_ignore_whitespace(&result, json); + + // Test decoding of JSON + let result = serde_json::from_str::(json).unwrap(); + let sample_other = SUT::sample_other(); + assert_ne!(result, sample_other); + assert_eq!(result.signed_subintent, sample_other.signed_subintent); // same signedPartialTransaction + assert_eq!( + sample_other + .expiration_timestamp + .duration_since(result.expiration_timestamp) + .whole_milliseconds(), + 123 + ); // only difference in expiration_timestamp are the 123 milliseconds + } + + #[test] + fn json_failures() { + // Test without signedPartialTransaction + let json = r#" + { + "subintentHash": "subtxid_sim1kdwxe9mkpgn2n5zplvh4kcu0d69k5qcz679xhxfa8ulcjtjqsvtq799xkn", + "expirationTimestamp": 1730999831257 + } + "#; + let result = serde_json::from_str::(json); + assert!(result.is_err()); + + // Test with invalid signedPartialTransaction + let json = r#" + { + "signedPartialTransaction": "invalid", + "subintentHash": "subtxid_sim1kdwxe9mkpgn2n5zplvh4kcu0d69k5qcz679xhxfa8ulcjtjqsvtq799xkn", + "expirationTimestamp": 1730999831257 + } + "#; + let result = serde_json::from_str::(json); + assert!(result.is_err()); + + // Test without expirationTimestamp + let json = r#" + { + "signedPartialTransaction": "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202200202000", + "subintentHash": "subtxid_sim1kdwxe9mkpgn2n5zplvh4kcu0d69k5qcz679xhxfa8ulcjtjqsvtq799xkn" + } + "#; + let result = serde_json::from_str::(json); + assert!(result.is_err()); } } diff --git a/crates/sargon/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs b/crates/sargon/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs index 60d665615..65deb9ffb 100644 --- a/crates/sargon/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs +++ b/crates/sargon/src/system/sargon_os/pre_authorization/sargon_os_pre_authorization_status.rs @@ -9,11 +9,16 @@ impl SargonOS { pub async fn poll_pre_authorization_status( &self, intent_hash: SubintentHash, - expiration: DappToWalletInteractionSubintentExpiration, + expiration_timetsamp: Timestamp, ) -> Result { - self.poll_pre_authorization_status_with_delays(intent_hash, expiration) - .await - .map(|(status, _)| status) + let seconds_until_expiration = + self.seconds_until_expiration(expiration_timetsamp); + self.poll_pre_authorization_status_with_delays( + intent_hash, + seconds_until_expiration, + ) + .await + .map(|(status, _)| status) } } @@ -27,7 +32,7 @@ impl SargonOS { async fn poll_pre_authorization_status_with_delays( &self, intent_hash: SubintentHash, - expiration: DappToWalletInteractionSubintentExpiration, + seconds_until_expiration: u64, ) -> Result<(PreAuthorizationStatus, Vec)> { let network_id = self.current_network_id()?; let gateway_client = GatewayClient::new( @@ -37,8 +42,7 @@ impl SargonOS { // We are going to play safe and leave an extra second to make sure we check the status one second after it has theoretically expired. // This is to avoid considering expired a subintent that got committed in the last instant. - let seconds_until_expiration = - self.seconds_until_expiration(expiration) + 1; + let seconds_until_expiration = seconds_until_expiration + 1; let mut delays = Vec::new(); let mut delay_duration = POLLING_DELAY_INCREMENT_IN_SECONDS; @@ -111,23 +115,10 @@ impl SargonOS { } /// Returns the remaining seconds until the subintent expires. - fn seconds_until_expiration( - &self, - expiration: DappToWalletInteractionSubintentExpiration, - ) -> u64 { - match expiration { - DappToWalletInteractionSubintentExpiration::AtTime(at_time) => { - let current_time = seconds_since_unix_epoch(); - if at_time.unix_timestamp_seconds > current_time { - at_time.unix_timestamp_seconds - current_time - } else { - 0 // Avoid overflow in case we check after expiration - } - } - DappToWalletInteractionSubintentExpiration::AfterDelay(delay) => { - delay.expire_after_seconds - } - } + fn seconds_until_expiration(&self, expiration_timestamp: Timestamp) -> u64 { + expiration_timestamp + .duration_since(Timestamp::now_utc()) + .as_seconds_f64() as u64 } } @@ -151,7 +142,7 @@ mod poll_pre_authorization_status_with_delays { let result = simulate_poll_status( 2, SSR::committed_success(intent_hash.to_string()), - expiration_after_delay(10), + 10, ) .await .unwrap(); @@ -167,13 +158,10 @@ mod poll_pre_authorization_status_with_delays { async fn success_exactly_at_expiration() { // This test will simulate the case where the subintent expires in 9 seconds, and we only get // the `CommittedSuccess` on the fourth request (after 9 seconds of delay). - let result = simulate_poll_status( - 3, - SSR::sample_committed_success(), - expiration_after_delay(9), - ) - .await - .unwrap(); + let result = + simulate_poll_status(3, SSR::sample_committed_success(), 9) + .await + .unwrap(); // The result is Success assert!(matches!(result.0, PreAuthorizationStatus::Success { .. })); @@ -186,13 +174,10 @@ mod poll_pre_authorization_status_with_delays { async fn success_immediately_after_expiration() { // This test will simulate the case where the subintent expires in 10 seconds, and we only get // the `CommittedSuccess` on the fifth request (after 11 seconds of delay). - let result = simulate_poll_status( - 4, - SSR::sample_committed_success(), - expiration_after_delay(10), - ) - .await - .unwrap(); + let result = + simulate_poll_status(4, SSR::sample_committed_success(), 10) + .await + .unwrap(); // The result is Success assert!(matches!(result.0, PreAuthorizationStatus::Success { .. })); @@ -205,13 +190,10 @@ mod poll_pre_authorization_status_with_delays { async fn expired() { // This test will simulate the case where the subintent expires in 10 seconds, and we only get // the `CommittedSuccess` on the sixth request (that is never made). - let result = simulate_poll_status( - 5, - SSR::sample_committed_success(), - expiration_after_delay(10), - ) - .await - .unwrap(); + let result = + simulate_poll_status(5, SSR::sample_committed_success(), 10) + .await + .unwrap(); // The result is Expired assert_eq!(result.0, PreAuthorizationStatus::Expired); @@ -224,13 +206,9 @@ mod poll_pre_authorization_status_with_delays { async fn success_without_transaction_intent_hash() { // This test will simulate the case where the GW returns a corrupted `CommittedSuccess` response // that is missing the TX id. - let result = simulate_poll_status( - 0, - SSR::committed_success(None), - expiration_after_delay(10), - ) - .await - .expect_err("Expected an error"); + let result = simulate_poll_status(0, SSR::committed_success(None), 10) + .await + .expect_err("Expected an error"); // The result an Unknown error assert_eq!(result, CommonError::Unknown); @@ -243,7 +221,7 @@ mod poll_pre_authorization_status_with_delays { let result = simulate_poll_status( 0, SSR::committed_success("not an intent hash".to_string()), - expiration_after_delay(10), + 10, ) .await .expect_err("Expected an error"); @@ -276,7 +254,7 @@ mod poll_pre_authorization_status_with_delays { let result = os .poll_pre_authorization_status_with_delays( SubintentHash::sample(), - expiration_after_delay(10), + 10, ) .await .unwrap(); @@ -294,7 +272,7 @@ mod poll_pre_authorization_status_with_delays { async fn simulate_poll_status( unknown_count: u64, last: SubintentStatusResponse, - expiration: DappToWalletInteractionSubintentExpiration, + seconds_until_expiration: u64, ) -> Result<(PreAuthorizationStatus, Vec)> { let mut responses = vec![SSR::sample_unknown(); unknown_count as usize]; responses.push(last); @@ -310,16 +288,10 @@ mod poll_pre_authorization_status_with_delays { os.poll_pre_authorization_status_with_delays( SubintentHash::sample(), - expiration, + seconds_until_expiration, ) .await } - - fn expiration_after_delay( - seconds: u64, - ) -> DappToWalletInteractionSubintentExpiration { - DappToWalletInteractionSubintentExpiration::AfterDelay(seconds.into()) - } } #[cfg(test)] @@ -330,41 +302,21 @@ mod seconds_until_expiration_tests { type SUT = SargonOS; #[actix_rt::test] - async fn at_time() { + async fn seconds_until() { let os = boot().await; + let seconds = 100; // Test the case where the expiration is set to a specific time in the past - let unix_seconds = 100; - let expiration = DappToWalletInteractionSubintentExpiration::AtTime( - unix_seconds.into(), - ); - let result = os.seconds_until_expiration(expiration); + let expiration_timestamp = + Timestamp::now_utc().sub(Duration::from_secs(seconds)); + let result = os.seconds_until_expiration(expiration_timestamp); assert_eq!(result, 0); // Test the case where the expiration is set to a specific time in the future - let now = seconds_since_unix_epoch(); - let diff = 50; - let expiration = DappToWalletInteractionSubintentExpiration::AtTime( - (now + diff).into(), - ); - - let result = os.seconds_until_expiration(expiration); - assert_eq!(result, diff); - } - - #[actix_rt::test] - async fn after_delay() { - // This test will simulate the case where the expiration is set to a delay after the current time - let delay = 100; - let expiration = DappToWalletInteractionSubintentExpiration::AfterDelay( - delay.into(), - ); - - let os = boot().await; - let result = os.seconds_until_expiration(expiration); - - // The result should be the same as the delay - assert_eq!(result, delay); + let expiration_timestamp = + Timestamp::now_utc().add(Duration::from_secs(seconds)); + let result = os.seconds_until_expiration(expiration_timestamp); + assert!(seconds - result <= 1); // Less than 1s difference, needed since the test is not instant and 1s may have passed. } async fn boot() -> Arc { diff --git a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v2/signed_subintent.rs b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v2/signed_subintent.rs index 29bfe4766..e6a84cf22 100644 --- a/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v2/signed_subintent.rs +++ b/crates/sargon/src/wrapped_radix_engine_toolkit/low_level/v2/signed_subintent.rs @@ -31,6 +31,10 @@ impl SignedSubintent { ) .expect("Compiling after initialization is always valid") } + + pub fn decompiling(compiled: Vec) -> Result { + decompile_signed_subintent(compiled) + } } fn into_scrypto( @@ -56,6 +60,30 @@ fn into_scrypto( } } +fn from_scrypto( + signed_partial_transaction: ScryptoSignedPartialTransaction, +) -> Result { + let subintent = Subintent::try_from( + signed_partial_transaction + .partial_transaction + .root_subintent, + )?; + let intent_signatures_v1 = ScryptoIntentSignatures { + signatures: signed_partial_transaction + .root_subintent_signatures + .signatures, + }; + let subintent_signatures = IntentSignatures::try_from(( + intent_signatures_v1, + subintent.hash().hash, + ))?; + let signed_subintnet = SignedSubintent { + subintent, + subintent_signatures, + }; + Ok(signed_subintnet) +} + fn compile_signed_subintent_with( subintent: &Subintent, subintent_signatures: &IntentSignatures, @@ -80,6 +108,24 @@ fn compile_signed_subintent( }) } +fn decompile_signed_subintent( + signed_subintent: Vec, +) -> Result { + let result = RET_decompile_signed_partial_tx(&signed_subintent).map_err( + |e| match e { + sbor::DecodeError::MaxDepthExceeded(max) => { + CommonError::InvalidTransactionMaxSBORDepthExceeded { + max: max as u16, + } + } + _ => CommonError::InvalidSignedIntentFailedToEncode { + underlying: format!("{:?}", e), + }, + }, + )?; + from_scrypto(result) +} + impl From for ScryptoSignedPartialTransaction { fn from(val: SignedSubintent) -> Self { into_scrypto(&val.subintent, &val.subintent_signatures) diff --git a/crates/sargon/tests/vectors/main.rs b/crates/sargon/tests/vectors/main.rs index 72da9eb6c..32a4525e7 100644 --- a/crates/sargon/tests/vectors/main.rs +++ b/crates/sargon/tests/vectors/main.rs @@ -722,8 +722,8 @@ mod dapp_to_wallet_interaction_tests { #[cfg(test)] mod wallet_to_dapp_interaction_tests { - use super::*; + use iso8601_timestamp::Timestamp; use serde_json::Value; #[test] @@ -962,13 +962,15 @@ mod wallet_to_dapp_interaction_tests { ), ); - let pre_authorization_response_items = WalletToDappInteractionResponseItems::PreAuthorization( - WalletToDappInteractionPreAuthorizationResponseItems { - response: WalletToDappInteractionSubintentResponseItem { - encoded_signed_partial_transaction: "4d220e03210221012105210607f20a00000000000000000a0a000000000000002200002200000ab168de3a00000000202000220000202000202200202100202201000121012007410001598e989470d125dafac276b95bb1ba21e2ee8e0beb0547599335f83b48a0a830cd6a956a54421039cef5fb7e492ebaa315f751a2dd5b74bd9cebbda997ec12202000".to_string(), - } - } - ); + let pre_authorization_response_items = + WalletToDappInteractionResponseItems::PreAuthorization( + WalletToDappInteractionPreAuthorizationResponseItems { + response: WalletToDappInteractionSubintentResponseItem::new( + SignedSubintent::sample_other(), + Timestamp::sample(), + ), + }, + ); let pre_authorization_response = WalletToDappInteractionResponse::Success(