Skip to content
Draft
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
15 changes: 15 additions & 0 deletions crates/configs/src/native_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ pub struct NativePriceConfig {
/// estimators but that will not have any further effect.
#[serde(default = "default_results_required")]
pub results_required: NonZeroUsize,

/// Whether to publish every native price estimate and competition winner to
/// the event bus. Requires the event bus to be configured; only meaningful
/// for a component that owns a native price cache (i.e. the autopilot),
/// since a component that forwards its lookups elsewhere would publish one
/// event per request rather than per price refresh.
#[serde(default)]
pub publish_events: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, we should just publish and filter later, so this should be removed

}

impl Default for NativePriceConfig {
Expand All @@ -48,6 +56,7 @@ impl Default for NativePriceConfig {
approximation_tokens: Default::default(),
cache: Default::default(),
results_required: default_results_required(),
publish_events: false,
}
}
}
Expand Down Expand Up @@ -86,12 +95,15 @@ mod tests {
["0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002"],
]

publish-events = true

[cache]
max-age = "5m"
concurrent-requests = 4
"#;
let config: NativePriceConfig = toml::from_str(toml).unwrap();
assert_eq!(config.approximation_tokens.len(), 1);
assert!(config.publish_events);
assert_eq!(config.cache.max_age, Duration::from_secs(300));
assert_eq!(
config.cache.concurrent_requests,
Expand All @@ -111,6 +123,7 @@ mod tests {
config.cache.concurrent_requests,
NonZeroUsize::new(1).unwrap()
);
assert!(!config.publish_events);
}

#[test]
Expand Down Expand Up @@ -139,6 +152,7 @@ mod tests {
concurrent_requests: NonZeroUsize::new(8).unwrap(),
},
results_required: default_results_required(),
publish_events: true,
};

let serialized = toml::to_string_pretty(&config).unwrap();
Expand All @@ -148,6 +162,7 @@ mod tests {
config.approximation_tokens,
deserialized.approximation_tokens,
);
assert_eq!(config.publish_events, deserialized.publish_events);
assert_eq!(config.cache.max_age, deserialized.cache.max_age);
assert_eq!(
config.cache.concurrent_requests,
Expand Down
147 changes: 147 additions & 0 deletions crates/event-bus-dto/schemas/events.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,100 @@
{
"nativePriceEstimate": {
"$defs": {
"NativePriceEstimateEvent": {
"description": "Emitted once per estimator taking part in a native price competition, as\nsoon as that estimator returns. Because the native price cache absorbs the\nvast majority of lookups, these events describe the price *refreshes* that\nactually reached an estimator, not every native price the protocol used.",
"properties": {
"elapsed": {
"description": "Wall-clock time the estimator actually spent, in milliseconds.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"estimator": {
"type": "string"
},
"result": {
"$ref": "#/$defs/NativePriceResult"
},
"timeout": {
"description": "Timeout granted to the estimator's competition stage, in milliseconds.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"token": {
"description": "Token the price was estimated for (hex-encoded, including the `0x`\nprefix). For tokens configured to be approximated by another token this\nis the approximation token, i.e. the one actually priced.",
"type": "string"
}
},
"required": [
"token",
"timeout",
"elapsed",
"estimator",
"result"
],
"type": "object"
},
"NativePriceResult": {
"anyOf": [
{
"properties": {
"price": {
"description": "Amount of native token needed to buy 1 unit of the token. Always a\nnormal, positive float: malformed prices are reported as errors.",
"format": "double",
"type": "number"
}
},
"required": [
"price"
],
"type": "object"
},
{
"properties": {
"error": {
"type": "string"
}
},
"required": [
"error"
],
"type": "object"
}
]
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "JSON envelope wrapping every event published to the bus. Consumers can\nrely on `version` to evolve their parsers, on `timestamp` for ordering,\nand on `requestId` to correlate events emitted while serving a single\ninbound request (e.g. all the price estimates and the resulting quote of\none quote request share the same `requestId`).",
"properties": {
"body": {
"$ref": "#/$defs/NativePriceEstimateEvent"
},
"requestId": {
"type": [
"string",
"null"
]
},
"timestamp": {
"description": "RFC3339 timestamp (millisecond precision, UTC) of when the event was\npublished.",
"format": "date-time",
"type": "string"
},
"version": {
"const": "v1",
"type": "string"
}
},
"required": [
"version",
"timestamp",
"body"
],
"title": "Envelope",
"type": "object"
},
"priceEstimate": {
"$defs": {
"EstimateResult": {
Expand Down Expand Up @@ -316,6 +412,57 @@
"title": "Envelope",
"type": "object"
},
"winningNativePriceEstimate": {
"$defs": {
"WinningNativePriceEstimateEvent": {
"description": "Emitted once a native price competition has picked the winning estimate,\ni.e. the price that gets cached and used to price orders. Its job is\ncorrelation: among the [`crate::NativePriceEstimateEvent`]s emitted for the\nsame token around the same time, the winning one is the one whose\n`estimator` matches this event.\n\nAt most one is emitted per competition, as all estimators may have errored\nin which case there is no winner.",
"properties": {
"estimator": {
"description": "Name of the estimator whose price estimate won the competition.",
"type": "string"
},
"token": {
"description": "Token the price was estimated for (hex-encoded, including the `0x`\nprefix).",
"type": "string"
}
},
"required": [
"token",
"estimator"
],
"type": "object"
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "JSON envelope wrapping every event published to the bus. Consumers can\nrely on `version` to evolve their parsers, on `timestamp` for ordering,\nand on `requestId` to correlate events emitted while serving a single\ninbound request (e.g. all the price estimates and the resulting quote of\none quote request share the same `requestId`).",
"properties": {
"body": {
"$ref": "#/$defs/WinningNativePriceEstimateEvent"
},
"requestId": {
"type": [
"string",
"null"
]
},
"timestamp": {
"description": "RFC3339 timestamp (millisecond precision, UTC) of when the event was\npublished.",
"format": "date-time",
"type": "string"
},
"version": {
"const": "v1",
"type": "string"
}
},
"required": [
"version",
"timestamp",
"body"
],
"title": "Envelope",
"type": "object"
},
"winningPriceEstimate": {
"$defs": {
"OrderKind": {
Expand Down
8 changes: 7 additions & 1 deletion crates/event-bus-dto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@
//! `event-bus-schemas` CLI emit JSON schemas for the full set of events.

pub mod envelope;
pub mod native_price_estimate;
pub mod price_estimate;
pub mod query;
pub mod quote_computed;
pub mod quote_requested;
pub mod winning_native_price_estimate;
pub mod winning_price_estimate;

pub use {
envelope::{ENVELOPE_VERSION, Envelope},
native_price_estimate::NativePriceEstimateEvent,
price_estimate::PriceEstimateEvent,
query::{OrderKind, QueryFields},
quote_computed::QuoteComputedEvent,
quote_requested::QuoteRequestedEvent,
winning_native_price_estimate::WinningNativePriceEstimateEvent,
winning_price_estimate::WinningPriceEstimateEvent,
};
use {schemars::JsonSchema, serde::Serialize};
Expand Down Expand Up @@ -54,6 +58,8 @@ pub fn schemas() -> Vec<(&'static str, schemars::Schema)> {
PriceEstimateEvent,
QuoteRequestedEvent,
QuoteComputedEvent,
WinningPriceEstimateEvent
WinningPriceEstimateEvent,
NativePriceEstimateEvent,
WinningNativePriceEstimateEvent
]
}
76 changes: 76 additions & 0 deletions crates/event-bus-dto/src/native_price_estimate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use {crate::Event, schemars::JsonSchema, serde::Serialize};

/// Emitted once per estimator taking part in a native price competition, as
/// soon as that estimator returns. Because the native price cache absorbs the
/// vast majority of lookups, these events describe the price *refreshes* that
/// actually reached an estimator, not every native price the protocol used.
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct NativePriceEstimateEvent {
/// Token the price was estimated for (hex-encoded, including the `0x`
/// prefix). For tokens configured to be approximated by another token this
/// is the approximation token, i.e. the one actually priced.
pub token: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addresses are serialized correctly and take less space than the string, replace the type

/// Timeout granted to the estimator's competition stage, in milliseconds.
pub timeout: u64,
/// Wall-clock time the estimator actually spent, in milliseconds.
pub elapsed: u64,
pub estimator: String,
pub result: NativePriceResult,
}

impl Event for NativePriceEstimateEvent {
const SUBJECT: &'static str = "nativePriceEstimate";
}

#[derive(Serialize, JsonSchema)]
#[serde(untagged)]
pub enum NativePriceResult {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the regular Result<f64, Error> would work here

Don't forget to assign this to an estimator 🤔

Ok {
/// Amount of native token needed to buy 1 unit of the token. Always a
/// normal, positive float: malformed prices are reported as errors.
price: f64,
},
Err {
error: String,
},
}

#[cfg(test)]
mod tests {
use {super::*, serde_json::json};

#[test]
fn matches_wire_format() {
let event = NativePriceEstimateEvent {
token: "0x01".into(),
timeout: 5000,
elapsed: 12,
estimator: "CoinGecko".into(),
result: NativePriceResult::Ok { price: 1.5e-13 },
};
assert_eq!(
serde_json::to_value(&event).unwrap(),
json!({
"token": "0x01",
"timeout": 5000,
"elapsed": 12,
"estimator": "CoinGecko",
"result": {
"price": 1.5e-13,
},
}),
);
}

#[test]
fn error_variant_is_untagged() {
let result = NativePriceResult::Err {
error: "boom".into(),
};
assert_eq!(
serde_json::to_value(&result).unwrap(),
json!({ "error": "boom" }),
);
}
}
43 changes: 43 additions & 0 deletions crates/event-bus-dto/src/winning_native_price_estimate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use {crate::Event, schemars::JsonSchema, serde::Serialize};

/// Emitted once a native price competition has picked the winning estimate,
/// i.e. the price that gets cached and used to price orders. Its job is
/// correlation: among the [`crate::NativePriceEstimateEvent`]s emitted for the
/// same token around the same time, the winning one is the one whose
/// `estimator` matches this event.
///
/// At most one is emitted per competition, as all estimators may have errored
/// in which case there is no winner.
#[derive(Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct WinningNativePriceEstimateEvent {
/// Token the price was estimated for (hex-encoded, including the `0x`
/// prefix).
pub token: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto on the token/address thing

/// Name of the estimator whose price estimate won the competition.
pub estimator: String,
}

impl Event for WinningNativePriceEstimateEvent {
const SUBJECT: &'static str = "winningNativePriceEstimate";
}

#[cfg(test)]
mod tests {
use {super::*, serde_json::json};

#[test]
fn matches_wire_format() {
let event = WinningNativePriceEstimateEvent {
token: "0x01".into(),
estimator: "CoinGecko".into(),
};
assert_eq!(
serde_json::to_value(&event).unwrap(),
json!({
"token": "0x01",
"estimator": "CoinGecko",
}),
);
}
}
Loading
Loading