Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions crates/cow-venue/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,24 @@ impl OrderBuilder {
}
}

/// Set the absolute `validTo` unix-seconds expiry, overriding the
/// constructor argument.
#[must_use]
pub const fn valid_to(mut self, secs: u32) -> Self {
self.body.valid_to = secs;
self
}

/// Expire `duration` seconds after `now`, saturating at `u32::MAX`.
/// `now` is the tick's block timestamp, not a wall clock: `valid_to`
/// is hashed into the submission dedup key, so a wall clock would
/// break reconcile-replay idempotency.
#[must_use]
pub const fn valid_for(mut self, now: u32, duration: u32) -> Self {
self.body.valid_to = now.saturating_add(duration);
self
}

/// Deliver the buy token to `receiver` instead of the owner.
#[must_use]
pub const fn receiver(mut self, receiver: Address) -> Self {
Expand Down Expand Up @@ -371,6 +389,48 @@ mod tests {
assert_eq!(built.kind, OrderKind::Sell);
}

#[test]
fn valid_to_setter_overrides_the_constructor_argument() {
let built = OrderBody::sell(
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1,
)
.valid_to(0x1234_5678)
.build();
assert_eq!(built.valid_to, 0x1234_5678);
}

#[test]
fn valid_for_adds_the_duration_to_now() {
let built = OrderBody::sell(
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1,
)
.valid_for(1_700_000_000, 3_600)
.build();
assert_eq!(built.valid_to, 1_700_003_600);
}

#[test]
fn valid_for_saturates_on_overflow() {
let built = OrderBody::sell(
SellToken(Address::repeat_byte(0x11)),
U256::from(1u64),
BuyToken(Address::repeat_byte(0x22)),
U256::from(2u64),
1,
)
.valid_for(u32::MAX - 10, 3_600)
.build();
assert_eq!(built.valid_to, u32::MAX);
}

#[test]
fn order_body_borsh_round_trips() {
let body = sample();
Expand Down
11 changes: 6 additions & 5 deletions modules/twap-monitor/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,15 +463,16 @@ mod tests {
on_block(host, &CowClient::with_transport(venue), block)
}

/// `validTo` a given number of seconds from now. The constructor's
/// client-side max-horizon policy reads the wall clock (not the
/// block clock), so test orders must expire relative to it.
fn valid_to_in(seconds: u64) -> u32 {
/// `validTo` a given number of seconds from the wall clock, the
/// same saturating `now + seconds` the builder's `valid_for` applies.
fn valid_to_in(seconds: u32) -> u32 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock is after the epoch")
.as_secs();
u32::try_from(now + seconds).expect("test validTo fits u32")
u32::try_from(now)
.expect("wall clock fits u32")
.saturating_add(seconds)
}

fn sample_params() -> ConditionalOrderParams {
Expand Down
Loading