From 9d5d6419da426b0777058738f96ffe79438bc966 Mon Sep 17 00:00:00 2001 From: matiasbzurovski <164921079+matiasbzurovski@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:49:20 +0100 Subject: [PATCH] Determine expiration from SignedSubintent (#282) * WIP * Swift extension * poll with Instant * bump * bring back test --- Cargo.lock | 4 +- apple/Sources/Sargon/Util/Instant+Date.swift | 8 ++ .../Tests/Utils/Extensions/Instant+Date.swift | 18 +++++ crates/sargon-uniffi/Cargo.toml | 2 +- .../pre_authorization/pre_authorization.rs | 4 +- .../sargon_os_pre_authorization_status.rs | 4 +- crates/sargon/Cargo.toml | 2 +- crates/sargon/src/core/types/instant.rs | 6 +- .../pre_authorization/pre_authorization.rs | 16 +--- .../pre_authorization/subintent_response.rs | 80 +++++++++++-------- .../sargon_os_pre_authorization_status.rs | 20 +++-- crates/sargon/tests/vectors/main.rs | 4 +- 12 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 apple/Sources/Sargon/Util/Instant+Date.swift create mode 100644 apple/Tests/Utils/Extensions/Instant+Date.swift diff --git a/Cargo.lock b/Cargo.lock index c86e376e7..56a856826 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.67" +version = "1.1.68" dependencies = [ "actix-rt", "aes-gcm", @@ -2814,7 +2814,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.67" +version = "1.1.68" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/apple/Sources/Sargon/Util/Instant+Date.swift b/apple/Sources/Sargon/Util/Instant+Date.swift new file mode 100644 index 000000000..2cd49c88f --- /dev/null +++ b/apple/Sources/Sargon/Util/Instant+Date.swift @@ -0,0 +1,8 @@ +import Foundation +import SargonUniFFI + +extension Instant { + public var date: Date { + .init(timeIntervalSince1970: TimeInterval(secondsSinceUnixEpoch)) + } +} diff --git a/apple/Tests/Utils/Extensions/Instant+Date.swift b/apple/Tests/Utils/Extensions/Instant+Date.swift new file mode 100644 index 000000000..cb8d1a399 --- /dev/null +++ b/apple/Tests/Utils/Extensions/Instant+Date.swift @@ -0,0 +1,18 @@ +import Foundation +import SargonUniFFI +import XCTest + +final class InstantTests: TestCase { + typealias SUT = Instant + + func testDate() throws { + var sut = SUT.init(secondsSinceUnixEpoch: 0) + XCTAssertEqual(sut.date, Date(timeIntervalSince1970: 0)) + + sut = .init(secondsSinceUnixEpoch: 500) + XCTAssertEqual(sut.date, Date(timeIntervalSince1970: 500)) + + sut = .init(secondsSinceUnixEpoch: 1000) + XCTAssertEqual(sut.date.timeIntervalSince1970, 1000) + } +} diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index 9881b52b5..9c062dfff 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.67" +version = "1.1.68" 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 4854301b5..4c970f59c 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 @@ -13,17 +13,15 @@ pub struct WalletToDappInteractionSubintentResponseItem { pub signed_subintent: SignedSubintent, /// The timestamp at which the subintent expires - pub expiration_timestamp: Timestamp, + pub expiration_timestamp: Instant, } #[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 4c4902f42..3ae1bdb5a 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_timestamp: Timestamp, + expiration_timestamp: Instant, ) -> Result { self.wrapped .poll_pre_authorization_status( intent_hash.into_internal(), - expiration_timestamp, + expiration_timestamp.into_internal(), ) .await .into_result() diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index b56436c1c..531e16e46 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.67" +version = "1.1.68" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/core/types/instant.rs b/crates/sargon/src/core/types/instant.rs index fa2e13407..c35d5be6d 100644 --- a/crates/sargon/src/core/types/instant.rs +++ b/crates/sargon/src/core/types/instant.rs @@ -55,11 +55,13 @@ impl From for Instant { impl HasSampleValues for Instant { fn sample() -> Self { - Self::from(0) + // matches Timestamp::sample() + Self::from(1694448356) } fn sample_other() -> Self { - Self::from(1) + // matches Timestamp::sample_other() + Self::from(1703438036) } } 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 7877896c7..8102a6a5c 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,15 +7,9 @@ pub struct WalletToDappInteractionPreAuthorizationResponseItems { } impl WalletToDappInteractionPreAuthorizationResponseItems { - pub fn new( - signed_subintent: SignedSubintent, - expiration_timestamp: Timestamp, - ) -> Self { + pub fn new(signed_subintent: SignedSubintent) -> Self { Self { - response: WalletToDappInteractionSubintentResponseItem::new( - signed_subintent, - expiration_timestamp, - ), + response: signed_subintent.into(), } } } @@ -52,10 +46,4 @@ mod tests { fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); } - - #[test] - fn new() { - 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 2bfa56d7b..867f41958 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 @@ -7,13 +7,13 @@ pub struct WalletToDappInteractionSubintentResponseItem { pub signed_subintent: SignedSubintent, /// The timestamp at which the subintent expires - pub expiration_timestamp: Timestamp, + pub expiration_timestamp: Instant, } impl WalletToDappInteractionSubintentResponseItem { pub fn new( signed_subintent: SignedSubintent, - expiration_timestamp: Timestamp, + expiration_timestamp: Instant, ) -> Self { Self { signed_subintent, @@ -29,11 +29,18 @@ impl WalletToDappInteractionSubintentResponseItem { 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 From for WalletToDappInteractionSubintentResponseItem { + fn from(value: SignedSubintent) -> Self { + let expiration_timestamp = value + .subintent + .header + .max_proposer_timestamp_exclusive + .expect( + "A SignedSubintent must have max_proposer_timestamp_exclusive", + ); + Self::new(value, expiration_timestamp) } } @@ -56,7 +63,7 @@ impl Serialize for WalletToDappInteractionSubintentResponseItem { )?; state.serialize_field( "expirationTimestamp", - &self.expiration_timestamp_seconds(), + &self.expiration_timestamp.seconds_since_unix_epoch, )?; state.end() } @@ -73,13 +80,13 @@ impl<'de> Deserialize<'de> for WalletToDappInteractionSubintentResponseItem { encoded_signed_partial_transaction: String, #[serde(rename = "expirationTimestamp")] - expiration_timestamp_seconds: u64, + expiration_timestamp_seconds: i64, } 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)); + let expiration_timestamp = + Instant::from(wrapped.expiration_timestamp_seconds); SignedSubintent::decompiling(decoded) .map_err(de::Error::custom) .map(|s| Self::new(s, expiration_timestamp)) @@ -88,11 +95,11 @@ impl<'de> Deserialize<'de> for WalletToDappInteractionSubintentResponseItem { impl HasSampleValues for WalletToDappInteractionSubintentResponseItem { fn sample() -> Self { - Self::new(SignedSubintent::sample(), Timestamp::sample()) + Self::new(SignedSubintent::sample(), Instant::sample()) } fn sample_other() -> Self { - Self::new(SignedSubintent::sample_other(), Timestamp::sample_other()) + Self::new(SignedSubintent::sample_other(), Instant::sample_other()) } } @@ -116,33 +123,18 @@ mod tests { #[test] 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#" + assert_eq_after_json_roundtrip( + &SUT::sample_other(), + 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] @@ -178,4 +170,28 @@ mod tests { let result = serde_json::from_str::(json); assert!(result.is_err()); } + + #[test] + fn from() { + let signed_subintent = SignedSubintent::sample(); + let result = SUT::from(signed_subintent.clone()); + assert_eq!(result.signed_subintent, signed_subintent); + assert_eq!( + result.expiration_timestamp, + signed_subintent + .subintent + .header + .max_proposer_timestamp_exclusive + .unwrap() + ); + } + + #[test] + #[should_panic( + expected = "A SignedSubintent must have max_proposer_timestamp_exclusive" + )] + fn from_without_max_proposer_timestamp() { + let signed_subintent = SignedSubintent::sample_other(); + let _ = SUT::from(signed_subintent); + } } 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 65deb9ffb..c9a6d9457 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,10 +9,10 @@ impl SargonOS { pub async fn poll_pre_authorization_status( &self, intent_hash: SubintentHash, - expiration_timetsamp: Timestamp, + expiration_timestamp: Instant, ) -> Result { let seconds_until_expiration = - self.seconds_until_expiration(expiration_timetsamp); + self.seconds_until_expiration(expiration_timestamp); self.poll_pre_authorization_status_with_delays( intent_hash, seconds_until_expiration, @@ -115,10 +115,14 @@ impl SargonOS { } /// Returns the remaining seconds until the subintent expires. - fn seconds_until_expiration(&self, expiration_timestamp: Timestamp) -> u64 { - expiration_timestamp - .duration_since(Timestamp::now_utc()) - .as_seconds_f64() as u64 + fn seconds_until_expiration(&self, expiration_timestamp: Instant) -> u64 { + let expiration = expiration_timestamp.seconds_since_unix_epoch as u64; + let now = seconds_since_unix_epoch(); + if expiration > now { + expiration - now + } else { + 0 + } } } @@ -309,13 +313,13 @@ mod seconds_until_expiration_tests { // Test the case where the expiration is set to a specific time in the past let expiration_timestamp = Timestamp::now_utc().sub(Duration::from_secs(seconds)); - let result = os.seconds_until_expiration(expiration_timestamp); + let result = os.seconds_until_expiration(expiration_timestamp.into()); assert_eq!(result, 0); // Test the case where the expiration is set to a specific time in the future let expiration_timestamp = Timestamp::now_utc().add(Duration::from_secs(seconds)); - let result = os.seconds_until_expiration(expiration_timestamp); + let result = os.seconds_until_expiration(expiration_timestamp.into()); assert!(seconds - result <= 1); // Less than 1s difference, needed since the test is not instant and 1s may have passed. } diff --git a/crates/sargon/tests/vectors/main.rs b/crates/sargon/tests/vectors/main.rs index 32a4525e7..e2402dd5a 100644 --- a/crates/sargon/tests/vectors/main.rs +++ b/crates/sargon/tests/vectors/main.rs @@ -723,7 +723,7 @@ 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] @@ -967,7 +967,7 @@ mod wallet_to_dapp_interaction_tests { WalletToDappInteractionPreAuthorizationResponseItems { response: WalletToDappInteractionSubintentResponseItem::new( SignedSubintent::sample_other(), - Timestamp::sample(), + Instant::sample(), ), }, );