use serde::{Deserialize, Serialize};
use crate::bond::BondOutcome;
use crate::route::Fill;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Promise {
MinOutput(u64),
Forecast { strike: f64, expect_above: bool },
}
pub trait Outcome: Send + Sync {
fn evaluate(&self, promise: &Promise) -> BondOutcome;
fn observed_unix(&self) -> u64;
}
impl Outcome for Fill {
fn evaluate(&self, promise: &Promise) -> BondOutcome {
match promise {
Promise::MinOutput(min) => {
if self.amount_out.raw >= *min {
BondOutcome::Honored
} else {
BondOutcome::Slashed
}
}
_ => BondOutcome::Slashed,
}
}
fn observed_unix(&self) -> u64 {
self.executed_unix
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Measurement {
pub value: f64,
pub observed_unix: u64,
}
impl Measurement {
pub fn new(value: f64, observed_unix: u64) -> Self {
Self {
value,
observed_unix,
}
}
}
impl Outcome for Measurement {
fn evaluate(&self, promise: &Promise) -> BondOutcome {
match promise {
Promise::Forecast {
strike,
expect_above,
} => {
let landed_above = self.value >= *strike;
if landed_above == *expect_above {
BondOutcome::Honored
} else {
BondOutcome::Slashed
}
}
_ => BondOutcome::Slashed,
}
}
fn observed_unix(&self) -> u64 {
self.observed_unix
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::Amount;
fn fill(amount_out: u64) -> Fill {
Fill {
amount_out: Amount::new(amount_out, 6),
executed_unix: 7,
}
}
#[test]
fn swap_fill_judges_min_output() {
let promise = Promise::MinOutput(1_000);
assert_eq!(fill(1_000).evaluate(&promise), BondOutcome::Honored);
assert_eq!(fill(1_500).evaluate(&promise), BondOutcome::Honored);
assert_eq!(fill(999).evaluate(&promise), BondOutcome::Slashed);
assert_eq!(fill(1_000).observed_unix(), 7);
}
#[test]
fn swap_fill_fails_safe_against_a_forecast_promise() {
let promise = Promise::Forecast {
strike: 100.0,
expect_above: true,
};
assert_eq!(fill(999_999).evaluate(&promise), BondOutcome::Slashed);
}
#[test]
fn measurement_judges_a_bullish_forecast() {
let promise = Promise::Forecast {
strike: 100.0,
expect_above: true,
};
assert_eq!(
Measurement::new(105.0, 0).evaluate(&promise),
BondOutcome::Honored
);
assert_eq!(
Measurement::new(95.0, 0).evaluate(&promise),
BondOutcome::Slashed
);
}
#[test]
fn measurement_judges_a_bearish_forecast() {
let promise = Promise::Forecast {
strike: 100.0,
expect_above: false,
};
assert_eq!(
Measurement::new(95.0, 0).evaluate(&promise),
BondOutcome::Honored
);
assert_eq!(
Measurement::new(100.0, 0).evaluate(&promise),
BondOutcome::Slashed
);
}
#[test]
fn measurement_fails_safe_against_a_swap_promise() {
assert_eq!(
Measurement::new(1.0, 0).evaluate(&Promise::MinOutput(0)),
BondOutcome::Slashed
);
}
}