Skip to content

Commit

Permalink
Quote with JIT orders API (#3083)
Browse files Browse the repository at this point in the history
# Description
Closes task n1 from #3082.

Introduces a new version of the driver's `/quote` response in the API
schema in order to support a gradual migration to the quote with jit
orders making the changes backward compatible.

The driver changes are expected in the future PRs.

# Changes
> Update the driver's OpenAPI schema quote object with an enum with the
old and new versions. Trade verifier returns error in case a new version
is received.

## How to test
Existing tests. I was thinking about how to test it properly, but it
still requires e2e tests. So, it should be implemented in the next PRs.
  • Loading branch information
squadgazzz authored Oct 28, 2024
1 parent 926a7ac commit f808db2
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 35 deletions.
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:
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:
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:
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>,
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,
}
}

0 comments on commit f808db2

Please sign in to comment.