Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quote with JIT orders API #3083

Merged
merged 9 commits into from
Oct 28, 2024
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
148 changes: 126 additions & 22 deletions crates/driver/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/QuoteResponse"
$ref: "#/components/schemas/QuoteResponseKind"
400:
$ref: "#/components/responses/BadRequest"
429:
Expand Down Expand Up @@ -292,31 +292,81 @@ components:
and bytes 52..56 valid to,
type: string
example: "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6"
QuoteResponse:
QuoteResponseKind:
oneOf:
- description: |
Successful Quote
- $ref: "#/components/schemas/LegacyQuoteResponse"
- $ref: "#/components/schemas/QuoteResponse"
- $ref: "#/components/schemas/Error"
LegacyQuoteResponse:
description: |
Successful Quote

The Solver knows how to fill the request with these parameters.
The Solver knows how to fill the request with these parameters.

If the request was of type `buy` then the response's buy amount has the same value as the
request's amount and the sell amount was filled in by the server. Vice versa for type
`sell`.
If the request was of type `buy` then the response's buy amount has the same value as the
request's amount and the sell amount was filled in by the server. Vice versa for type
`sell`.
type: object
properties:
amount:
$ref: "#/components/schemas/TokenAmount"
interactions:
type: array
items:
$ref: "#/components/schemas/Interaction"
solver:
description: The address of the solver that quoted this order.
$ref: "#/components/schemas/Address"
gas:
type: integer
description: How many units of gas this trade is estimated to cost.
txOrigin:
allOf:
- $ref: "#/components/schemas/Address"
description: Which `tx.origin` is required to make a quote simulation pass.
required:
- amount
- interactions
- solver
QuoteResponse:
description: |
Successful Quote with JIT orders support.

The Solver knows how to fill the request with these parameters.
type: object
properties:
clearingPrices:
description: |
Mapping of hex token address to the uniform clearing price.
type: object
properties:
amount:
$ref: "#/components/schemas/TokenAmount"
interactions:
type: array
items:
$ref: "#/components/schemas/Interaction"
solver:
description: The address of the solver that quoted this order.
$ref: "#/components/schemas/Address"
gas:
type: integer
description: How many units of gas this trade is estimated to cost.
- $ref: "#/components/schemas/Error"
additionalProperties:
$ref: "#/components/schemas/BigUint"
preInteractions:
type: array
items:
$ref: "#/components/schemas/Interaction"
interactions:
type: array
items:
$ref: "#/components/schemas/Interaction"
solver:
squadgazzz marked this conversation as resolved.
Show resolved Hide resolved
allOf:
- $ref: "#/components/schemas/Address"
description: The address of the solver that quoted this order.
gas:
type: integer
description: How many units of gas this trade is estimated to cost.
txOrigin:
allOf:
- $ref: "#/components/schemas/Address"
description: Which `tx.origin` is required to make a quote simulation pass.
jitOrders:
type: array
items:
$ref: "#/components/schemas/JitOrder"
required:
- clearingPrices
- solver
DateTime:
description: An ISO 8601 UTC date time string.
type: string
Expand Down Expand Up @@ -514,6 +564,60 @@ components:
$ref: "#/components/schemas/TokenAmount"
solver:
$ref: "#/components/schemas/Address"
JitOrder:
type: object
properties:
squadgazzz marked this conversation as resolved.
Show resolved Hide resolved
sellToken:
$ref: "#/components/schemas/Address"
buyToken:
$ref: "#/components/schemas/Address"
sellAmount:
$ref: "#/components/schemas/TokenAmount"
buyAmount:
$ref: "#/components/schemas/TokenAmount"
executedAmount:
$ref: "#/components/schemas/TokenAmount"
receiver:
$ref: "#/components/schemas/Address"
validTo:
type: integer
side:
type: string
enum: ["buy", "sell"]
partiallyFillable:
type: boolean
sellTokenSource:
type: string
enum: ["erc20", "internal", "external"]
buyTokenSource:
type: string
enum: ["erc20", "internal"]
appData:
squadgazzz marked this conversation as resolved.
Show resolved Hide resolved
type: string
signature:
description: |
Hex encoded bytes with `0x` prefix. The content depends on the `signingScheme`.
For `presign`, this should contain the address of the owner.
For `eip1271`, the signature should consist of `<owner_address><signature_bytes>`.
type: string
signingScheme:
type: string
enum: ["eip712", "ethsign", "presign", "eip1271"]
required:
- sellToken
- buyToken
- sellAmount
- buyAmount
- executedAmount
- receiver
- validTo
- side
- partiallyFillable
- sellTokenSource
- buyTokenSource
- appData
- signature
- signingScheme
Error:
description: Response on API errors.
type: object
Expand Down
105 changes: 92 additions & 13 deletions crates/shared/src/trade_finding/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,19 @@ impl ExternalTradeFinder {
.text()
.await
.map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?;
serde_json::from_str::<dto::Quote>(&text)
.map(Trade::from)
.map_err(|err| {
if let Ok(err) = serde_json::from_str::<dto::Error>(&text) {
PriceEstimationError::from(err)
} else {
PriceEstimationError::EstimatorInternal(anyhow!(err))
}
})
let quote = serde_json::from_str::<dto::QuoteKind>(&text).map_err(|err| {
if let Ok(err) = serde_json::from_str::<dto::Error>(&text) {
PriceEstimationError::from(err)
} else {
PriceEstimationError::EstimatorInternal(anyhow!(err))
}
})?;
match quote {
dto::QuoteKind::Legacy(quote) => Ok(Trade::from(quote)),
dto::QuoteKind::Regular(_) => Err(PriceEstimationError::EstimatorInternal(
anyhow!("Quote with JIT orders is not currently supported"),
)),
}
}
.boxed()
};
Expand All @@ -113,8 +117,8 @@ impl ExternalTradeFinder {
}
}

impl From<dto::Quote> for Trade {
fn from(quote: dto::Quote) -> Self {
impl From<dto::LegacyQuote> for Trade {
fn from(quote: dto::LegacyQuote) -> Self {
Self {
out_amount: quote.amount,
gas_estimate: quote.gas,
Expand Down Expand Up @@ -142,6 +146,16 @@ impl From<dto::Error> for PriceEstimationError {
}
}

impl From<dto::Interaction> for Interaction {
fn from(interaction: dto::Interaction) -> Self {
Self {
target: interaction.target,
value: interaction.value,
data: interaction.call_data,
}
}
}

#[async_trait::async_trait]
impl TradeFinding for ExternalTradeFinder {
async fn get_quote(&self, query: &Query) -> Result<Quote, TradeError> {
Expand All @@ -166,12 +180,17 @@ impl TradeFinding for ExternalTradeFinder {

mod dto {
use {
app_data::AppDataHash,
bytes_hex::BytesHex,
ethcontract::{H160, U256},
model::order::OrderKind,
model::{
order::{BuyTokenDestination, OrderKind, SellTokenSource},
signature::SigningScheme,
},
number::serialization::HexOrDecimalU256,
serde::{Deserialize, Serialize},
serde_with::serde_as,
std::collections::HashMap,
};

#[serde_as]
Expand All @@ -186,10 +205,19 @@ mod dto {
pub deadline: chrono::DateTime<chrono::Utc>,
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum QuoteKind {
Legacy(LegacyQuote),
#[allow(unused)]
Regular(Quote),
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Quote {
pub struct LegacyQuote {
#[serde_as(as = "HexOrDecimalU256")]
pub amount: U256,
pub interactions: Vec<Interaction>,
Expand All @@ -199,6 +227,24 @@ mod dto {
pub tx_origin: Option<H160>,
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(unused)]
pub struct Quote {
#[serde_as(as = "HashMap<_, HexOrDecimalU256>")]
pub clearing_prices: HashMap<H160, U256>,
#[serde(default)]
pub pre_interactions: Vec<Interaction>,
#[serde(default)]
pub interactions: Vec<Interaction>,
squadgazzz marked this conversation as resolved.
Show resolved Hide resolved
pub solver: H160,
pub gas: Option<u64>,
pub tx_origin: Option<H160>,
#[serde(default)]
pub jit_orders: Vec<JitOrder>,
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -210,11 +256,44 @@ mod dto {
pub call_data: Vec<u8>,
}

#[serde_as]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(unused)]
pub struct JitOrder {
pub buy_token: H160,
pub sell_token: H160,
#[serde_as(as = "HexOrDecimalU256")]
pub sell_amount: U256,
#[serde_as(as = "HexOrDecimalU256")]
pub buy_amount: U256,
#[serde_as(as = "HexOrDecimalU256")]
pub executed_amount: U256,
pub receiver: H160,
pub valid_to: u32,
pub app_data: AppDataHash,
pub side: Side,
pub partially_fillable: bool,
pub sell_token_source: SellTokenSource,
pub buy_token_destination: BuyTokenDestination,
#[serde_as(as = "BytesHex")]
pub signature: Vec<u8>,
pub signing_scheme: SigningScheme,
}

#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Error {
pub kind: String,
pub description: String,
}

#[serde_as]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Side {
Buy,
Sell,
}
}
Loading