Skip to content

Commit

Permalink
Determine expiration from SignedSubintent (#282)
Browse files Browse the repository at this point in the history
* WIP

* Swift extension

* poll with Instant

* bump

* bring back test
  • Loading branch information
matiasbzurovski authored Nov 29, 2024
1 parent a235128 commit 9d5d641
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 67 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions apple/Sources/Sargon/Util/Instant+Date.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation
import SargonUniFFI

extension Instant {
public var date: Date {
.init(timeIntervalSince1970: TimeInterval(secondsSinceUnixEpoch))
}
}
18 changes: 18 additions & 0 deletions apple/Tests/Utils/Extensions/Instant+Date.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
2 changes: 1 addition & 1 deletion crates/sargon-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ impl SargonOS {
pub async fn poll_pre_authorization_status(
&self,
intent_hash: SubintentHash,
expiration_timestamp: Timestamp,
expiration_timestamp: Instant,
) -> Result<PreAuthorizationStatus> {
self.wrapped
.poll_pre_authorization_status(
intent_hash.into_internal(),
expiration_timestamp,
expiration_timestamp.into_internal(),
)
.await
.into_result()
Expand Down
2 changes: 1 addition & 1 deletion crates/sargon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
6 changes: 4 additions & 2 deletions crates/sargon/src/core/types/instant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ impl From<Timestamp> 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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<SignedSubintent> 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)
}
}

Expand All @@ -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()
}
Expand All @@ -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))
Expand All @@ -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())
}
}

Expand All @@ -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::<SUT>(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]
Expand Down Expand Up @@ -178,4 +170,28 @@ mod tests {
let result = serde_json::from_str::<SUT>(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ impl SargonOS {
pub async fn poll_pre_authorization_status(
&self,
intent_hash: SubintentHash,
expiration_timetsamp: Timestamp,
expiration_timestamp: Instant,
) -> Result<PreAuthorizationStatus> {
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,
Expand Down Expand Up @@ -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
}
}
}

Expand Down Expand Up @@ -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.
}

Expand Down
4 changes: 2 additions & 2 deletions crates/sargon/tests/vectors/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -967,7 +967,7 @@ mod wallet_to_dapp_interaction_tests {
WalletToDappInteractionPreAuthorizationResponseItems {
response: WalletToDappInteractionSubintentResponseItem::new(
SignedSubintent::sample_other(),
Timestamp::sample(),
Instant::sample(),
),
},
);
Expand Down

0 comments on commit 9d5d641

Please sign in to comment.