From a423e578d4fb7e6bb5883a130ee928064455854c Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 24 Oct 2024 18:12:34 +0100 Subject: [PATCH 01/38] Quote with JIT orders API --- crates/driver/openapi.yml | 111 ++++++++++++++--- crates/shared/src/trade_finding/external.rs | 130 +++++++++++++++++--- 2 files changed, 207 insertions(+), 34 deletions(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 91f8037113..1a6e0b26d2 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -294,29 +294,63 @@ components: example: "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6" QuoteResponse: oneOf: - - description: | - Successful Quote + - $ref: "#/components/schemas/LegacyQuote" + - $ref: "#/components/schemas/QuoteWithJitOrders" + - $ref: "#/components/schemas/Error" + LegacyQuote: + 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`. + 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. + QuoteWithJitOrders: + description: | + Successful Quote - 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`. + 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: + 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. + jitOrders: + type: array + items: + $ref: "#/components/schemas/JitOrder" DateTime: description: An ISO 8601 UTC date time string. type: string @@ -514,6 +548,43 @@ 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"] + 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 ``. + type: string + signingScheme: + type: string + enum: ["eip712", "ethsign", "presign", "eip1271"] Error: description: Response on API errors. type: object diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index ecf651f08e..001ed0c3b1 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -93,15 +93,21 @@ impl ExternalTradeFinder { .text() .await .map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?; - serde_json::from_str::(&text) - .map(Trade::from) - .map_err(|err| { - if let Ok(err) = serde_json::from_str::(&text) { - PriceEstimationError::from(err) - } else { - PriceEstimationError::EstimatorInternal(anyhow!(err)) - } - }) + let quote = serde_json::from_str::(&text).map_err(|err| { + if let Ok(err) = serde_json::from_str::(&text) { + PriceEstimationError::from(err) + } else { + PriceEstimationError::EstimatorInternal(anyhow!(err)) + } + })?; + match quote { + dto::Quote::LegacyQuote(quote) => Ok(Trade::from(quote)), + dto::Quote::QuoteWithJitOrders(_) => { + Err(PriceEstimationError::EstimatorInternal(anyhow!( + "Quote with JIT orders is not currently supported" + ))) + } + } } .boxed() }; @@ -113,8 +119,8 @@ impl ExternalTradeFinder { } } -impl From for Trade { - fn from(quote: dto::Quote) -> Self { +impl From for Trade { + fn from(quote: dto::LegacyQuote) -> Self { Self { out_amount: quote.amount, gas_estimate: quote.gas, @@ -142,6 +148,16 @@ impl From for PriceEstimationError { } } +impl From 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 { @@ -166,12 +182,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::{de, Deserialize, Deserializer, Serialize}, serde_with::serde_as, + std::collections::HashMap, }; #[serde_as] @@ -186,17 +207,65 @@ mod dto { pub deadline: chrono::DateTime, } + #[serde_as] + #[derive(Clone, Debug)] + pub enum Quote { + LegacyQuote(LegacyQuote), + #[allow(unused)] + QuoteWithJitOrders(QuoteWithJITOrders), + } + + impl<'de> Deserialize<'de> for Quote { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: serde_json::Value = Deserialize::deserialize(deserializer)?; + + if value.get("clearingPrices").is_some() + || value.get("jitOrders").is_some() + || value.get("preInteractions").is_some() + { + Ok(Quote::QuoteWithJitOrders( + QuoteWithJITOrders::deserialize(value).map_err(de::Error::custom)?, + )) + } else { + Ok(Quote::LegacyQuote( + LegacyQuote::deserialize(value).map_err(de::Error::custom)?, + )) + } + } + } + + #[serde_as] + #[derive(Clone, Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct LegacyQuote { + #[serde_as(as = "HexOrDecimalU256")] + pub amount: U256, + pub interactions: Vec, + pub solver: H160, + pub gas: Option, + #[serde(default)] + pub tx_origin: Option, + } + #[serde_as] #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] - pub struct Quote { + #[allow(unused)] + pub struct QuoteWithJITOrders { #[serde_as(as = "HexOrDecimalU256")] pub amount: U256, + pub pre_interactions: Vec, pub interactions: Vec, pub solver: H160, pub gas: Option, #[serde(default)] pub tx_origin: Option, + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + pub clearing_prices: HashMap, + pub jit_orders: Vec, } #[serde_as] @@ -210,6 +279,30 @@ mod dto { pub call_data: Vec, } + #[serde_as] + #[derive(Clone, Debug, Default, 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 sell_token_source: SellTokenSource, + pub buy_token_destination: BuyTokenDestination, + #[serde_as(as = "BytesHex")] + pub signature: Vec, + pub signing_scheme: SigningScheme, + } + #[serde_as] #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -217,4 +310,13 @@ mod dto { pub kind: String, pub description: String, } + + #[serde_as] + #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum Side { + #[default] + Buy, + Sell, + } } From 0bf5393ed0a0aa1594d5e851d39fb7e51bb72ee3 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 24 Oct 2024 18:24:01 +0100 Subject: [PATCH 02/38] Updated description --- crates/driver/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 1a6e0b26d2..f1f0c5af94 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -322,7 +322,7 @@ components: description: How many units of gas this trade is estimated to cost. QuoteWithJitOrders: description: | - Successful Quote + Successful Quote with JIT orders support. The Solver knows how to fill the request with these parameters. type: object From 9cc9abe09e6326214b5c5705ee5d07149cff8422 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 25 Oct 2024 09:33:24 +0100 Subject: [PATCH 03/38] Redundant defaults --- crates/shared/src/trade_finding/external.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 001ed0c3b1..fff432e110 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -246,7 +246,6 @@ mod dto { pub interactions: Vec, pub solver: H160, pub gas: Option, - #[serde(default)] pub tx_origin: Option, } @@ -261,7 +260,6 @@ mod dto { pub interactions: Vec, pub solver: H160, pub gas: Option, - #[serde(default)] pub tx_origin: Option, #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, @@ -280,7 +278,7 @@ mod dto { } #[serde_as] - #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] + #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct JitOrder { @@ -312,10 +310,9 @@ mod dto { } #[serde_as] - #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] + #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub enum Side { - #[default] Buy, Sell, } From 898e5e73854c9a727491b6b743f52fd90108147d Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 25 Oct 2024 11:01:46 +0100 Subject: [PATCH 04/38] Redundant amount --- crates/shared/src/trade_finding/external.rs | 24 +++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index fff432e110..8390759f8c 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -101,12 +101,10 @@ impl ExternalTradeFinder { } })?; match quote { - dto::Quote::LegacyQuote(quote) => Ok(Trade::from(quote)), - dto::Quote::QuoteWithJitOrders(_) => { - Err(PriceEstimationError::EstimatorInternal(anyhow!( - "Quote with JIT orders is not currently supported" - ))) - } + dto::Quote::Legacy(quote) => Ok(Trade::from(quote)), + dto::Quote::WithJitOrders(_) => Err(PriceEstimationError::EstimatorInternal( + anyhow!("Quote with JIT orders is not currently supported"), + )), } } .boxed() @@ -210,9 +208,9 @@ mod dto { #[serde_as] #[derive(Clone, Debug)] pub enum Quote { - LegacyQuote(LegacyQuote), + Legacy(LegacyQuote), #[allow(unused)] - QuoteWithJitOrders(QuoteWithJITOrders), + WithJitOrders(QuoteWithJITOrders), } impl<'de> Deserialize<'de> for Quote { @@ -226,11 +224,11 @@ mod dto { || value.get("jitOrders").is_some() || value.get("preInteractions").is_some() { - Ok(Quote::QuoteWithJitOrders( + Ok(Quote::WithJitOrders( QuoteWithJITOrders::deserialize(value).map_err(de::Error::custom)?, )) } else { - Ok(Quote::LegacyQuote( + Ok(Quote::Legacy( LegacyQuote::deserialize(value).map_err(de::Error::custom)?, )) } @@ -254,15 +252,13 @@ mod dto { #[serde(rename_all = "camelCase")] #[allow(unused)] pub struct QuoteWithJITOrders { - #[serde_as(as = "HexOrDecimalU256")] - pub amount: U256, + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + pub clearing_prices: HashMap, pub pre_interactions: Vec, pub interactions: Vec, pub solver: H160, pub gas: Option, pub tx_origin: Option, - #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] - pub clearing_prices: HashMap, pub jit_orders: Vec, } From 5e1d001d4badb25f379681d0f0eb3187dc95b475 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 25 Oct 2024 17:02:07 +0100 Subject: [PATCH 05/38] Support jit orders quote --- crates/autopilot/src/run.rs | 1 + crates/e2e/tests/e2e/quote_verification.rs | 10 +- crates/orderbook/src/run.rs | 1 + crates/shared/src/price_estimation/factory.rs | 31 +-- .../src/price_estimation/trade_verifier.rs | 210 +++++++++++++++--- crates/shared/src/trade_finding.rs | 133 ++++++++++- crates/shared/src/trade_finding/external.rs | 90 ++++++-- 7 files changed, 396 insertions(+), 80 deletions(-) diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index c6747413b7..b8f8629da7 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -333,6 +333,7 @@ pub async fn run(args: Arguments) { code_fetcher: code_fetcher.clone(), }, ) + .await .expect("failed to initialize price estimator factory"); let native_price_estimator = price_estimator_factory diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index cc9cf2c132..25abe93a2a 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -13,7 +13,7 @@ use { Estimate, Verification, }, - trade_finding::{Interaction, Trade}, + trade_finding::{Interaction, LegacyTrade, Trade}, }, std::{str::FromStr, sync::Arc}, }; @@ -62,7 +62,9 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), 0.0, - ); + ) + .await + .unwrap(); let verify_trade = |tx_origin| { let verifier = verifier.clone(); @@ -86,7 +88,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { sell_token_source: SellTokenSource::Erc20, buy_token_destination: BuyTokenDestination::Erc20, }, - Trade { + Trade::Legacy(LegacyTrade { out_amount: 16380122291179526144u128.into(), gas_estimate: Some(225000), interactions: vec![Interaction { @@ -98,7 +100,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { solver: H160::from_str("0xe3067c7c27c1038de4e8ad95a83b927d23dfbd99") .unwrap(), tx_origin, - }, + }), ) .await } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 8e87611994..c12e832f29 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -274,6 +274,7 @@ pub async fn run(args: Arguments) { code_fetcher: code_fetcher.clone(), }, ) + .await .expect("failed to initialize price estimator factory"); let native_price_estimator = price_estimator_factory diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 37a2fe5f70..c3fc78983c 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -74,14 +74,14 @@ pub struct Components { } impl<'a> PriceEstimatorFactory<'a> { - pub fn new( + pub async fn new( args: &'a Arguments, shared_args: &'a arguments::Arguments, network: Network, components: Components, ) -> Result { Ok(Self { - trade_verifier: Self::trade_verifier(args, shared_args, &network, &components), + trade_verifier: Self::trade_verifier(args, shared_args, &network, &components).await?, args, network, components, @@ -89,13 +89,15 @@ impl<'a> PriceEstimatorFactory<'a> { }) } - fn trade_verifier( + async fn trade_verifier( args: &'a Arguments, shared_args: &arguments::Arguments, network: &Network, components: &Components, - ) -> Option> { - let web3 = network.simulation_web3.clone()?; + ) -> Result>> { + let Some(web3) = network.simulation_web3.clone() else { + return Ok(None); + }; let web3 = ethrpc::instrumented::instrument_with_label(&web3, "simulator".into()); let tenderly = shared_args @@ -112,14 +114,17 @@ impl<'a> PriceEstimatorFactory<'a> { None => Arc::new(web3.clone()), }; - Some(Arc::new(TradeVerifier::new( - web3, - simulator, - components.code_fetcher.clone(), - network.block_stream.clone(), - network.settlement, - network.native_token, - args.quote_inaccuracy_limit, + Ok(Some(Arc::new( + TradeVerifier::new( + web3, + simulator, + components.code_fetcher.clone(), + network.block_stream.clone(), + network.settlement, + network.native_token, + args.quote_inaccuracy_limit, + ) + .await?, ))) } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 22c8c2506b..c03c6d8135 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -5,7 +5,7 @@ use { code_simulation::CodeSimulating, encoded_settlement::{encode_trade, EncodedSettlement}, interaction::EncodedInteraction, - trade_finding::{Interaction, Trade}, + trade_finding::{external::dto, Interaction, Trade}, }, anyhow::{Context, Result}, contracts::{ @@ -21,6 +21,7 @@ use { model::{ order::{OrderData, OrderKind, BUY_ETH_ADDRESS}, signature::{Signature, SigningScheme}, + DomainSeparator, }, num::BigRational, number::{conversions::u256_to_big_rational, nonzero::U256 as NonZeroU256}, @@ -50,13 +51,14 @@ pub struct TradeVerifier { settlement: GPv2Settlement, native_token: H160, quote_inaccuracy_limit: BigRational, + domain_separator: DomainSeparator, } impl TradeVerifier { const DEFAULT_GAS: u64 = 8_000_000; const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000"); - pub fn new( + pub async fn new( web3: Web3, simulator: Arc, code_fetcher: Arc, @@ -64,17 +66,21 @@ impl TradeVerifier { settlement: H160, native_token: H160, quote_inaccuracy_limit: f64, - ) -> Self { - Self { + ) -> Result { + let settlement_contract = GPv2Settlement::at(&web3, settlement); + let domain_separator = + DomainSeparator(settlement_contract.domain_separator().call().await?.0); + Ok(Self { simulator, code_fetcher, block_stream, - settlement: GPv2Settlement::at(&web3, settlement), + settlement: settlement_contract, native_token, quote_inaccuracy_limit: BigRational::from_float(quote_inaccuracy_limit) .expect("can represent all finite values"), web3, - } + domain_separator, + }) } async fn verify_inner( @@ -82,6 +88,7 @@ impl TradeVerifier { query: &PriceQuery, verification: &Verification, trade: &Trade, + out_amount: &U256, ) -> Result { if verification.from.is_zero() { // Don't waste time on common simulations which will always fail. @@ -92,10 +99,17 @@ impl TradeVerifier { // Use `tx_origin` if response indicates that a special address is needed for // the simulation to pass. Otherwise just use the solver address. - let solver = trade.tx_origin.unwrap_or(trade.solver); + let solver = trade.tx_origin().unwrap_or(trade.solver()); let solver = dummy_contract!(Solver, solver); - let settlement = encode_settlement(query, verification, trade, self.native_token); + let settlement = encode_settlement( + query, + verification, + trade, + out_amount, + self.native_token, + &self.domain_separator, + )?; let settlement = add_balance_queries( settlement, query, @@ -117,7 +131,7 @@ impl TradeVerifier { let sell_amount = match query.kind { OrderKind::Sell => query.in_amount.get(), - OrderKind::Buy => trade.out_amount, + OrderKind::Buy => *out_amount, }; let simulation = solver @@ -168,11 +182,11 @@ impl TradeVerifier { // for a different `tx.origin` we need to pretend these // quotes actually simulated successfully to not lose these competitive quotes // when we enable quote verification in prod. - if trade.tx_origin == Some(H160::zero()) { + if trade.tx_origin() == Some(H160::zero()) { let estimate = Estimate { - out_amount: trade.out_amount, - gas: trade.gas_estimate.context("no gas estimate")?, - solver: trade.solver, + out_amount: *out_amount, + gas: trade.gas_estimate().context("no gas estimate")?, + solver: trade.solver(), verified: true, }; tracing::warn!( @@ -213,19 +227,24 @@ impl TradeVerifier { tracing::debug!( lost_buy_amount = %summary.buy_tokens_lost, lost_sell_amount = %summary.sell_tokens_lost, - gas_diff = ?trade.gas_estimate.unwrap_or_default().abs_diff(summary.gas_used.as_u64()), + gas_diff = ?trade.gas_estimate().unwrap_or_default().abs_diff(summary.gas_used.as_u64()), time = ?start.elapsed(), - promised_out_amount = ?trade.out_amount, + promised_out_amount = ?out_amount, verified_out_amount = ?summary.out_amount, - promised_gas = trade.gas_estimate, + promised_gas = trade.gas_estimate(), verified_gas = ?summary.gas_used, - out_diff = ?trade.out_amount.abs_diff(summary.out_amount), + out_diff = ?out_amount.abs_diff(summary.out_amount), ?query, ?verification, "verified quote", ); - ensure_quote_accuracy(&self.quote_inaccuracy_limit, query, trade.solver, &summary) + ensure_quote_accuracy( + &self.quote_inaccuracy_limit, + query, + trade.solver(), + &summary, + ) } /// Configures all the state overrides that are needed to mock the given @@ -268,10 +287,13 @@ impl TradeVerifier { // If the trade requires a special tx.origin we also need to fake the // authenticator and tx origin balance. - if trade.tx_origin.is_some_and(|origin| origin != trade.solver) { + if trade + .tx_origin() + .is_some_and(|origin| origin != trade.solver()) + { let (authenticator, balance) = futures::join!( self.settlement.authenticator().call(), - self.web3.eth().balance(trade.solver, None) + self.web3.eth().balance(trade.solver(), None) ); let authenticator = authenticator.context("could not fetch authenticator")?; overrides.insert( @@ -284,7 +306,7 @@ impl TradeVerifier { let balance = balance.context("could not fetch balance")?; solver_override.balance = Some(balance); } - overrides.insert(trade.tx_origin.unwrap_or(trade.solver), solver_override); + overrides.insert(trade.tx_origin().unwrap_or(trade.solver()), solver_override); Ok(overrides) } @@ -298,14 +320,25 @@ impl TradeVerifying for TradeVerifier { verification: &Verification, trade: Trade, ) -> Result { - match self.verify_inner(query, verification, &trade).await { + let out_amount = trade + .out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + ) + .context("failed to compute trade out amount")?; + match self + .verify_inner(query, verification, &trade, &out_amount) + .await + { Ok(verified) => Ok(verified), - Err(Error::SimulationFailed(err)) => match trade.gas_estimate { + Err(Error::SimulationFailed(err)) => match trade.gas_estimate() { Some(gas) => { let estimate = Estimate { - out_amount: trade.out_amount, + out_amount, gas, - solver: trade.solver, + solver: trade.solver(), verified: false, }; tracing::warn!( @@ -340,16 +373,18 @@ fn encode_settlement( query: &PriceQuery, verification: &Verification, trade: &Trade, + out_amount: &U256, native_token: H160, -) -> EncodedSettlement { - let mut trade_interactions = encode_interactions(&trade.interactions); + domain_separator: &DomainSeparator, +) -> Result { + let mut trade_interactions = encode_interactions(&trade.interactions()); if query.buy_token == BUY_ETH_ADDRESS { // Because the `driver` manages `WETH` unwraps under the hood the `TradeFinder` // does not have to emit unwraps to pay out `ETH` in a trade. // However, for the simulation to be successful this has to happen so we do it // ourselves here. let buy_amount = match query.kind { - OrderKind::Sell => trade.out_amount, + OrderKind::Sell => *out_amount, OrderKind::Buy => query.in_amount.get(), }; let weth = dummy_contract!(WETH9, native_token); @@ -358,10 +393,38 @@ fn encode_settlement( tracing::trace!("adding unwrap interaction for paying out ETH"); } - let tokens = vec![query.sell_token, query.buy_token]; - let clearing_prices = match query.kind { - OrderKind::Sell => vec![trade.out_amount, query.in_amount.get()], - OrderKind::Buy => vec![query.in_amount.get(), trade.out_amount], + let mut tokens = vec![query.sell_token, query.buy_token]; + let mut clearing_prices = match (trade, query.kind) { + (Trade::Legacy(_), OrderKind::Sell) => { + vec![*out_amount, query.in_amount.get()] + } + (Trade::Legacy(_), OrderKind::Buy) => { + vec![query.in_amount.get(), *out_amount] + } + (Trade::WithJitOrders(trade), OrderKind::Sell) => { + vec![ + *trade + .clearing_prices + .get(&query.sell_token) + .context("sell token clearing price is missing")?, + *trade + .clearing_prices + .get(&query.buy_token) + .context("buy token clearing price is missing")?, + ] + } + (Trade::WithJitOrders(trade), OrderKind::Buy) => { + vec![ + *trade + .clearing_prices + .get(&query.buy_token) + .context("buy token clearing price is missing")?, + *trade + .clearing_prices + .get(&query.sell_token) + .context("sell token clearing price is missing")?, + ] + } }; // Configure the most disadvantageous trade possible (while taking possible @@ -371,7 +434,7 @@ fn encode_settlement( let (sell_amount, buy_amount) = match query.kind { OrderKind::Sell => (query.in_amount.get(), 0.into()), OrderKind::Buy => ( - trade.out_amount.max(U256::from(u128::MAX)), + (*out_amount).max(U256::from(u128::MAX)), query.in_amount.get(), ), }; @@ -400,16 +463,89 @@ fn encode_settlement( &query.in_amount.get(), ); - EncodedSettlement { + let mut trades = vec![encoded_trade]; + + if let Trade::WithJitOrders(trade) = trade { + for jit_order in trade.jit_orders.iter() { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: 0.into(), + kind: match &jit_order.side { + dto::Side::Buy => OrderKind::Buy, + dto::Side::Sell => OrderKind::Sell, + }, + partially_fillable: false, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = match jit_order.signing_scheme { + SigningScheme::Eip1271 => { + let (owner, signature) = jit_order.signature.split_at(20); + let owner = H160::from_slice(owner); + let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; + (owner, signature) + } + SigningScheme::PreSign => { + let owner = H160::from_slice(&jit_order.signature); + let signature = + Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; + (owner, signature) + } + _ => { + let signature = + Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; + let owner = signature + .recover(domain_separator, &order_data.hash_struct())? + .context("could not recover the owner")? + .signer; + (owner, signature) + } + }; + + tokens.push(jit_order.sell_token); + tokens.push(jit_order.buy_token); + clearing_prices.push( + *trade + .clearing_prices + .get(&jit_order.sell_token) + .context("jit order sell token clearing price is missing")?, + ); + clearing_prices.push( + *trade + .clearing_prices + .get(&jit_order.buy_token) + .context("jit order buy token clearing price is missing")?, + ); + + trades.push(encode_trade( + &order_data, + &signature, + owner, + tokens.len() - 2, + tokens.len() - 1, + &jit_order.executed_amount, + )); + } + } + let mut pre_interactions = verification.pre_interactions.clone(); + pre_interactions.extend(trade.pre_interactions().iter().cloned()); + + Ok(EncodedSettlement { tokens, clearing_prices, - trades: vec![encoded_trade], + trades, interactions: [ - encode_interactions(&verification.pre_interactions), + encode_interactions(&pre_interactions), trade_interactions, encode_interactions(&verification.post_interactions), ], - } + }) } /// Adds the interactions that are only needed to query important balances diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 3e5e8bfdf4..bcebfe91e5 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -4,12 +4,19 @@ pub mod external; use { - crate::price_estimation::{PriceEstimationError, Query}, - anyhow::Result, + crate::{ + conversions::U256Ext, + price_estimation::{PriceEstimationError, Query}, + trade_finding::external::dto, + }, + anyhow::{Context, Result}, derivative::Derivative, ethcontract::{contract::MethodBuilder, tokens::Tokenize, web3::Transport, Bytes, H160, U256}, - model::interaction::InteractionData, + model::{interaction::InteractionData, order::OrderKind}, + num::CheckedDiv, + number::conversions::big_rational_to_u256, serde::Serialize, + std::{collections::HashMap, ops::Mul}, thiserror::Error, }; @@ -32,9 +39,73 @@ pub struct Quote { pub solver: H160, } -/// A trade. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Trade { + Legacy(LegacyTrade), + WithJitOrders(TradeWithJitOrders), +} + +impl Trade { + pub fn gas_estimate(&self) -> Option { + match self { + Trade::Legacy(trade) => trade.gas_estimate, + Trade::WithJitOrders(trade) => trade.gas_estimate, + } + } + + pub fn solver(&self) -> H160 { + match self { + Trade::Legacy(trade) => trade.solver, + Trade::WithJitOrders(trade) => trade.solver, + } + } + + pub fn tx_origin(&self) -> Option { + match self { + Trade::Legacy(trade) => trade.tx_origin, + Trade::WithJitOrders(trade) => trade.tx_origin, + } + } + + pub fn out_amount( + &self, + buy_token: &H160, + sell_token: &H160, + in_amount: &U256, + order_kind: &OrderKind, + ) -> Result { + match self { + Trade::Legacy(trade) => Ok(trade.out_amount), + Trade::WithJitOrders(trade) => { + trade.out_amount(buy_token, sell_token, in_amount, order_kind) + } + } + } + + pub fn interactions(&self) -> Vec { + match self { + Trade::Legacy(trade) => trade.interactions.clone(), + Trade::WithJitOrders(trade) => trade.interactions.clone(), + } + } + + pub fn pre_interactions(&self) -> Vec { + match self { + Trade::Legacy(_) => Vec::new(), + Trade::WithJitOrders(trade) => trade.pre_interactions.clone(), + } + } +} + +impl Default for Trade { + fn default() -> Self { + Trade::Legacy(LegacyTrade::default()) + } +} + +/// A legacy trade. #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Trade { +pub struct LegacyTrade { /// For sell orders: how many buy_tokens this trade will produce. /// For buy orders: how many sell_tokens this trade will cost. pub out_amount: U256, @@ -49,6 +120,58 @@ pub struct Trade { pub tx_origin: Option, } +/// A trade with JIT orders. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TradeWithJitOrders { + pub clearing_prices: HashMap, + /// How many units of gas this trade will roughly cost. + pub gas_estimate: Option, + pub pre_interactions: Vec, + /// Interactions needed to produce the expected trade amount. + pub interactions: Vec, + /// Which solver provided this trade. + pub solver: H160, + /// If this is set the quote verification need to use this as the + /// `tx.origin` to make the quote pass the simulation. + pub tx_origin: Option, + pub jit_orders: Vec, +} + +impl TradeWithJitOrders { + pub fn out_amount( + &self, + buy_token: &H160, + sell_token: &H160, + in_amount: &U256, + order_kind: &OrderKind, + ) -> Result { + let sell_price = self + .clearing_prices + .get(sell_token) + .context("clearing sell price missing")? + .to_big_rational(); + let buy_price = self + .clearing_prices + .get(buy_token) + .context("clearing buy price missing")? + .to_big_rational(); + let order_amount = in_amount.to_big_rational(); + + let out_amount = match order_kind { + OrderKind::Sell => order_amount + .mul(&sell_price) + .checked_div(&buy_price) + .context("div by zero: buy price")?, + OrderKind::Buy => order_amount + .mul(&buy_price) + .checked_div(&sell_price) + .context("div by zero: sell price")?, + }; + + big_rational_to_u256(&out_amount).context("out amount is not a valid U256") + } +} + /// Data for a raw GPv2 interaction. #[derive(Clone, PartialEq, Eq, Hash, Default, Serialize, Derivative)] #[derivative(Debug)] diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index fff432e110..5c3aaf3f99 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -4,7 +4,15 @@ use { crate::{ price_estimation::{PriceEstimationError, Query}, request_sharing::RequestSharing, - trade_finding::{Interaction, Quote, Trade, TradeError, TradeFinding}, + trade_finding::{ + Interaction, + LegacyTrade, + Quote, + Trade, + TradeError, + TradeFinding, + TradeWithJitOrders, + }, }, anyhow::{anyhow, Context}, ethrpc::block_stream::CurrentBlockWatcher, @@ -93,21 +101,15 @@ impl ExternalTradeFinder { .text() .await .map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?; - let quote = serde_json::from_str::(&text).map_err(|err| { - if let Ok(err) = serde_json::from_str::(&text) { - PriceEstimationError::from(err) - } else { - PriceEstimationError::EstimatorInternal(anyhow!(err)) - } - })?; - match quote { - dto::Quote::LegacyQuote(quote) => Ok(Trade::from(quote)), - dto::Quote::QuoteWithJitOrders(_) => { - Err(PriceEstimationError::EstimatorInternal(anyhow!( - "Quote with JIT orders is not currently supported" - ))) - } - } + serde_json::from_str::(&text) + .map(Trade::from) + .map_err(|err| { + if let Ok(err) = serde_json::from_str::(&text) { + PriceEstimationError::from(err) + } else { + PriceEstimationError::EstimatorInternal(anyhow!(err)) + } + }) } .boxed() }; @@ -119,7 +121,16 @@ impl ExternalTradeFinder { } } -impl From for Trade { +impl From for Trade { + fn from(quote: dto::Quote) -> Self { + match quote { + dto::Quote::LegacyQuote(quote) => Trade::Legacy(quote.into()), + dto::Quote::QuoteWithJitOrders(quote) => Trade::WithJitOrders(quote.into()), + } + } +} + +impl From for LegacyTrade { fn from(quote: dto::LegacyQuote) -> Self { Self { out_amount: quote.amount, @@ -139,6 +150,36 @@ impl From for Trade { } } +impl From for TradeWithJitOrders { + fn from(quote: dto::QuoteWithJITOrders) -> Self { + Self { + clearing_prices: quote.clearing_prices, + gas_estimate: quote.gas, + pre_interactions: quote + .pre_interactions + .into_iter() + .map(|interaction| Interaction { + target: interaction.target, + value: interaction.value, + data: interaction.call_data, + }) + .collect(), + interactions: quote + .interactions + .into_iter() + .map(|interaction| Interaction { + target: interaction.target, + value: interaction.value, + data: interaction.call_data, + }) + .collect(), + solver: quote.solver, + tx_origin: quote.tx_origin, + jit_orders: quote.jit_orders, + } + } +} + impl From for PriceEstimationError { fn from(value: dto::Error) -> Self { match value.kind.as_str() { @@ -165,13 +206,20 @@ impl TradeFinding for ExternalTradeFinder { // reuse the same logic here. let trade = self.get_trade(query).await?; let gas_estimate = trade - .gas_estimate + .gas_estimate() .context("no gas estimate") .map_err(TradeError::Other)?; Ok(Quote { - out_amount: trade.out_amount, + out_amount: trade + .out_amount( + &query.buy_token, + &query.sell_token, + &query.in_amount.get(), + &query.kind, + ) + .map_err(TradeError::Other)?, gas_estimate, - solver: trade.solver, + solver: trade.solver(), }) } @@ -180,7 +228,7 @@ impl TradeFinding for ExternalTradeFinder { } } -mod dto { +pub(crate) mod dto { use { app_data::AppDataHash, bytes_hex::BytesHex, From 0937d4e377a2cbbf34cc052a95af6a77403e019b Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 11:45:23 +0000 Subject: [PATCH 06/38] Address rc --- crates/driver/openapi.yml | 33 +++++++++++++---- crates/shared/src/trade_finding/external.rs | 41 ++++++--------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index f1f0c5af94..551b48a923 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -48,7 +48,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/QuoteResponse" + $ref: "#/components/schemas/QuoteResponseKind" 400: $ref: "#/components/responses/BadRequest" 429: @@ -292,12 +292,12 @@ components: and bytes 52..56 valid to, type: string example: "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6" - QuoteResponse: + QuoteResponseKind: oneOf: - - $ref: "#/components/schemas/LegacyQuote" - - $ref: "#/components/schemas/QuoteWithJitOrders" + - $ref: "#/components/schemas/LegacyQuoteResponse" + - $ref: "#/components/schemas/QuoteResponse" - $ref: "#/components/schemas/Error" - LegacyQuote: + LegacyQuoteResponse: description: | Successful Quote @@ -320,7 +320,15 @@ components: gas: type: integer description: How many units of gas this trade is estimated to cost. - QuoteWithJitOrders: + 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. @@ -342,15 +350,24 @@ components: items: $ref: "#/components/schemas/Interaction" solver: + allOf: + - $ref: "#/components/schemas/Address" 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. jitOrders: type: array items: $ref: "#/components/schemas/JitOrder" + required: + - clearingPrices + - interactions + - solver DateTime: description: An ISO 8601 UTC date time string. type: string @@ -568,6 +585,8 @@ components: side: type: string enum: ["buy", "sell"] + partiallyFillable: + type: boolean sellTokenSource: type: string enum: ["erc20", "internal", "external"] diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 8390759f8c..3536ecf0ce 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -93,7 +93,7 @@ impl ExternalTradeFinder { .text() .await .map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?; - let quote = serde_json::from_str::(&text).map_err(|err| { + let quote = serde_json::from_str::(&text).map_err(|err| { if let Ok(err) = serde_json::from_str::(&text) { PriceEstimationError::from(err) } else { @@ -101,8 +101,8 @@ impl ExternalTradeFinder { } })?; match quote { - dto::Quote::Legacy(quote) => Ok(Trade::from(quote)), - dto::Quote::WithJitOrders(_) => Err(PriceEstimationError::EstimatorInternal( + dto::QuoteKind::Legacy(quote) => Ok(Trade::from(quote)), + dto::QuoteKind::Regular(_) => Err(PriceEstimationError::EstimatorInternal( anyhow!("Quote with JIT orders is not currently supported"), )), } @@ -188,7 +188,7 @@ mod dto { signature::SigningScheme, }, number::serialization::HexOrDecimalU256, - serde::{de, Deserialize, Deserializer, Serialize}, + serde::{Deserialize, Serialize}, serde_with::serde_as, std::collections::HashMap, }; @@ -206,33 +206,11 @@ mod dto { } #[serde_as] - #[derive(Clone, Debug)] - pub enum Quote { + #[derive(Clone, Debug, Deserialize)] + pub enum QuoteKind { Legacy(LegacyQuote), #[allow(unused)] - WithJitOrders(QuoteWithJITOrders), - } - - impl<'de> Deserialize<'de> for Quote { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value: serde_json::Value = Deserialize::deserialize(deserializer)?; - - if value.get("clearingPrices").is_some() - || value.get("jitOrders").is_some() - || value.get("preInteractions").is_some() - { - Ok(Quote::WithJitOrders( - QuoteWithJITOrders::deserialize(value).map_err(de::Error::custom)?, - )) - } else { - Ok(Quote::Legacy( - LegacyQuote::deserialize(value).map_err(de::Error::custom)?, - )) - } - } + Regular(Quote), } #[serde_as] @@ -251,14 +229,16 @@ mod dto { #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] #[allow(unused)] - pub struct QuoteWithJITOrders { + pub struct Quote { #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, + #[serde(default)] pub pre_interactions: Vec, pub interactions: Vec, pub solver: H160, pub gas: Option, pub tx_origin: Option, + #[serde(default)] pub jit_orders: Vec, } @@ -290,6 +270,7 @@ mod dto { 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")] From e759f42435636f38e877e02a4deb954e24446c3d Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 12:08:12 +0000 Subject: [PATCH 07/38] Restore the custom quote deserializer --- crates/shared/src/trade_finding/external.rs | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 3536ecf0ce..044602c6e2 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -188,7 +188,7 @@ mod dto { signature::SigningScheme, }, number::serialization::HexOrDecimalU256, - serde::{Deserialize, Serialize}, + serde::{de, Deserialize, Deserializer, Serialize}, serde_with::serde_as, std::collections::HashMap, }; @@ -206,13 +206,35 @@ mod dto { } #[serde_as] - #[derive(Clone, Debug, Deserialize)] + #[derive(Clone, Debug)] pub enum QuoteKind { Legacy(LegacyQuote), #[allow(unused)] Regular(Quote), } + impl<'de> Deserialize<'de> for QuoteKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: serde_json::Value = Deserialize::deserialize(deserializer)?; + + if value.get("clearingPrices").is_some() + || value.get("jitOrders").is_some() + || value.get("preInteractions").is_some() + { + Ok(QuoteKind::Regular( + Quote::deserialize(value).map_err(de::Error::custom)?, + )) + } else { + Ok(QuoteKind::Legacy( + LegacyQuote::deserialize(value).map_err(de::Error::custom)?, + )) + } + } + } + #[serde_as] #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -222,6 +244,7 @@ mod dto { pub interactions: Vec, pub solver: H160, pub gas: Option, + #[serde(default)] pub tx_origin: Option, } From e7b756acc0ceb6c2d84ba979bfbf6e184a6aa362 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 12:10:56 +0000 Subject: [PATCH 08/38] Address rc --- crates/driver/openapi.yml | 16 +++++++++++++++- crates/shared/src/trade_finding/external.rs | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 551b48a923..215f42c81e 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -366,7 +366,6 @@ components: $ref: "#/components/schemas/JitOrder" required: - clearingPrices - - interactions - solver DateTime: description: An ISO 8601 UTC date time string. @@ -604,6 +603,21 @@ components: 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 diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 044602c6e2..7028490c6b 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -257,6 +257,7 @@ mod dto { pub clearing_prices: HashMap, #[serde(default)] pub pre_interactions: Vec, + #[serde(default)] pub interactions: Vec, pub solver: H160, pub gas: Option, From f40fafbecb3bc8d5131da1cb35bb986be28569ce Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 12:31:59 +0000 Subject: [PATCH 09/38] Serde untagged --- crates/shared/src/trade_finding/external.rs | 27 +++------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 7028490c6b..8758847731 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -188,7 +188,7 @@ mod dto { signature::SigningScheme, }, number::serialization::HexOrDecimalU256, - serde::{de, Deserialize, Deserializer, Serialize}, + serde::{Deserialize, Serialize}, serde_with::serde_as, std::collections::HashMap, }; @@ -206,35 +206,14 @@ mod dto { } #[serde_as] - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Deserialize)] + #[serde(untagged)] pub enum QuoteKind { Legacy(LegacyQuote), #[allow(unused)] Regular(Quote), } - impl<'de> Deserialize<'de> for QuoteKind { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let value: serde_json::Value = Deserialize::deserialize(deserializer)?; - - if value.get("clearingPrices").is_some() - || value.get("jitOrders").is_some() - || value.get("preInteractions").is_some() - { - Ok(QuoteKind::Regular( - Quote::deserialize(value).map_err(de::Error::custom)?, - )) - } else { - Ok(QuoteKind::Legacy( - LegacyQuote::deserialize(value).map_err(de::Error::custom)?, - )) - } - } - } - #[serde_as] #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] From 18686fecf71eb4c6a8549980889cede753472b8d Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 12:45:01 +0000 Subject: [PATCH 10/38] Fixes after merge --- crates/e2e/tests/e2e/quote_verification.rs | 4 +- .../src/price_estimation/trade_verifier.rs | 27 +++++++------ crates/shared/src/trade_finding.rs | 40 +++++++++---------- crates/shared/src/trade_finding/external.rs | 18 ++++----- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 25abe93a2a..d12f249ab3 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -13,7 +13,7 @@ use { Estimate, Verification, }, - trade_finding::{Interaction, LegacyTrade, Trade}, + trade_finding::{Interaction, LegacyTrade, TradeKind}, }, std::{str::FromStr, sync::Arc}, }; @@ -88,7 +88,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { sell_token_source: SellTokenSource::Erc20, buy_token_destination: BuyTokenDestination::Erc20, }, - Trade::Legacy(LegacyTrade { + TradeKind::Legacy(LegacyTrade { out_amount: 16380122291179526144u128.into(), gas_estimate: Some(225000), interactions: vec![Interaction { diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index c03c6d8135..5a68c45315 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -5,7 +5,7 @@ use { code_simulation::CodeSimulating, encoded_settlement::{encode_trade, EncodedSettlement}, interaction::EncodedInteraction, - trade_finding::{external::dto, Interaction, Trade}, + trade_finding::{external::dto, Interaction, TradeKind}, }, anyhow::{Context, Result}, contracts::{ @@ -31,12 +31,13 @@ use { #[async_trait::async_trait] pub trait TradeVerifying: Send + Sync + 'static { - /// Verifies if the proposed [`Trade`] actually fulfills the [`PriceQuery`]. + /// Verifies if the proposed [`TradeKind`] actually fulfills the + /// [`PriceQuery`]. async fn verify( &self, query: &PriceQuery, verification: &Verification, - trade: Trade, + trade: TradeKind, ) -> Result; } @@ -87,7 +88,7 @@ impl TradeVerifier { &self, query: &PriceQuery, verification: &Verification, - trade: &Trade, + trade: &TradeKind, out_amount: &U256, ) -> Result { if verification.from.is_zero() { @@ -252,7 +253,7 @@ impl TradeVerifier { async fn prepare_state_overrides( &self, verification: &Verification, - trade: &Trade, + trade: &TradeKind, ) -> Result> { // Set up mocked trader. let mut overrides = hashmap! { @@ -318,7 +319,7 @@ impl TradeVerifying for TradeVerifier { &self, query: &PriceQuery, verification: &Verification, - trade: Trade, + trade: TradeKind, ) -> Result { let out_amount = trade .out_amount( @@ -372,7 +373,7 @@ fn encode_interactions(interactions: &[Interaction]) -> Vec fn encode_settlement( query: &PriceQuery, verification: &Verification, - trade: &Trade, + trade: &TradeKind, out_amount: &U256, native_token: H160, domain_separator: &DomainSeparator, @@ -395,13 +396,13 @@ fn encode_settlement( let mut tokens = vec![query.sell_token, query.buy_token]; let mut clearing_prices = match (trade, query.kind) { - (Trade::Legacy(_), OrderKind::Sell) => { + (TradeKind::Legacy(_), OrderKind::Sell) => { vec![*out_amount, query.in_amount.get()] } - (Trade::Legacy(_), OrderKind::Buy) => { + (TradeKind::Legacy(_), OrderKind::Buy) => { vec![query.in_amount.get(), *out_amount] } - (Trade::WithJitOrders(trade), OrderKind::Sell) => { + (TradeKind::Regular(trade), OrderKind::Sell) => { vec![ *trade .clearing_prices @@ -413,7 +414,7 @@ fn encode_settlement( .context("buy token clearing price is missing")?, ] } - (Trade::WithJitOrders(trade), OrderKind::Buy) => { + (TradeKind::Regular(trade), OrderKind::Buy) => { vec![ *trade .clearing_prices @@ -465,7 +466,7 @@ fn encode_settlement( let mut trades = vec![encoded_trade]; - if let Trade::WithJitOrders(trade) = trade { + if let TradeKind::Regular(trade) = trade { for jit_order in trade.jit_orders.iter() { let order_data = OrderData { sell_token: jit_order.sell_token, @@ -480,7 +481,7 @@ fn encode_settlement( dto::Side::Buy => OrderKind::Buy, dto::Side::Sell => OrderKind::Sell, }, - partially_fillable: false, + partially_fillable: jit_order.partially_fillable, sell_token_balance: jit_order.sell_token_source, buy_token_balance: jit_order.buy_token_destination, }; diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index bcebfe91e5..3aad80630a 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -28,7 +28,7 @@ use { #[async_trait::async_trait] pub trait TradeFinding: Send + Sync + 'static { async fn get_quote(&self, query: &Query) -> Result; - async fn get_trade(&self, query: &Query) -> Result; + async fn get_trade(&self, query: &Query) -> Result; } /// A quote. @@ -40,30 +40,30 @@ pub struct Quote { } #[derive(Clone, Debug, Eq, PartialEq)] -pub enum Trade { +pub enum TradeKind { Legacy(LegacyTrade), - WithJitOrders(TradeWithJitOrders), + Regular(Trade), } -impl Trade { +impl TradeKind { pub fn gas_estimate(&self) -> Option { match self { - Trade::Legacy(trade) => trade.gas_estimate, - Trade::WithJitOrders(trade) => trade.gas_estimate, + TradeKind::Legacy(trade) => trade.gas_estimate, + TradeKind::Regular(trade) => trade.gas_estimate, } } pub fn solver(&self) -> H160 { match self { - Trade::Legacy(trade) => trade.solver, - Trade::WithJitOrders(trade) => trade.solver, + TradeKind::Legacy(trade) => trade.solver, + TradeKind::Regular(trade) => trade.solver, } } pub fn tx_origin(&self) -> Option { match self { - Trade::Legacy(trade) => trade.tx_origin, - Trade::WithJitOrders(trade) => trade.tx_origin, + TradeKind::Legacy(trade) => trade.tx_origin, + TradeKind::Regular(trade) => trade.tx_origin, } } @@ -75,8 +75,8 @@ impl Trade { order_kind: &OrderKind, ) -> Result { match self { - Trade::Legacy(trade) => Ok(trade.out_amount), - Trade::WithJitOrders(trade) => { + TradeKind::Legacy(trade) => Ok(trade.out_amount), + TradeKind::Regular(trade) => { trade.out_amount(buy_token, sell_token, in_amount, order_kind) } } @@ -84,22 +84,22 @@ impl Trade { pub fn interactions(&self) -> Vec { match self { - Trade::Legacy(trade) => trade.interactions.clone(), - Trade::WithJitOrders(trade) => trade.interactions.clone(), + TradeKind::Legacy(trade) => trade.interactions.clone(), + TradeKind::Regular(trade) => trade.interactions.clone(), } } pub fn pre_interactions(&self) -> Vec { match self { - Trade::Legacy(_) => Vec::new(), - Trade::WithJitOrders(trade) => trade.pre_interactions.clone(), + TradeKind::Legacy(_) => Vec::new(), + TradeKind::Regular(trade) => trade.pre_interactions.clone(), } } } -impl Default for Trade { +impl Default for TradeKind { fn default() -> Self { - Trade::Legacy(LegacyTrade::default()) + TradeKind::Legacy(LegacyTrade::default()) } } @@ -122,7 +122,7 @@ pub struct LegacyTrade { /// A trade with JIT orders. #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct TradeWithJitOrders { +pub struct Trade { pub clearing_prices: HashMap, /// How many units of gas this trade will roughly cost. pub gas_estimate: Option, @@ -137,7 +137,7 @@ pub struct TradeWithJitOrders { pub jit_orders: Vec, } -impl TradeWithJitOrders { +impl Trade { pub fn out_amount( &self, buy_token: &H160, diff --git a/crates/shared/src/trade_finding/external.rs b/crates/shared/src/trade_finding/external.rs index 3b93d002fd..3f7714cdb1 100644 --- a/crates/shared/src/trade_finding/external.rs +++ b/crates/shared/src/trade_finding/external.rs @@ -11,7 +11,7 @@ use { Trade, TradeError, TradeFinding, - TradeWithJitOrders, + TradeKind, }, }, anyhow::{anyhow, Context}, @@ -28,7 +28,7 @@ pub struct ExternalTradeFinder { /// Utility to make sure no 2 identical requests are in-flight at the same /// time. Instead of issuing a duplicated request this awaits the /// response of the in-flight request. - sharing: RequestSharing>>, + sharing: RequestSharing>>, /// Client to issue http requests with. client: Client, @@ -58,7 +58,7 @@ impl ExternalTradeFinder { /// Queries the `/quote` endpoint of the configured driver and deserializes /// the result into a Quote or Trade. - async fn shared_query(&self, query: &Query) -> Result { + async fn shared_query(&self, query: &Query) -> Result { let fut = move |query: &Query| { let order = dto::Order { sell_token: query.sell_token, @@ -102,7 +102,7 @@ impl ExternalTradeFinder { .await .map_err(|err| PriceEstimationError::EstimatorInternal(anyhow!(err)))?; serde_json::from_str::(&text) - .map(Trade::from) + .map(TradeKind::from) .map_err(|err| { if let Ok(err) = serde_json::from_str::(&text) { PriceEstimationError::from(err) @@ -121,11 +121,11 @@ impl ExternalTradeFinder { } } -impl From for Trade { +impl From for TradeKind { fn from(quote: dto::QuoteKind) -> Self { match quote { - dto::Quote::LegacyQuote(quote) => Trade::Legacy(quote.into()), - dto::Quote::QuoteWithJitOrders(quote) => Trade::WithJitOrders(quote.into()), + dto::QuoteKind::Legacy(quote) => TradeKind::Legacy(quote.into()), + dto::QuoteKind::Regular(quote) => TradeKind::Regular(quote.into()), } } } @@ -150,7 +150,7 @@ impl From for LegacyTrade { } } -impl From for TradeWithJitOrders { +impl From for Trade { fn from(quote: dto::Quote) -> Self { Self { clearing_prices: quote.clearing_prices, @@ -223,7 +223,7 @@ impl TradeFinding for ExternalTradeFinder { }) } - async fn get_trade(&self, query: &Query) -> Result { + async fn get_trade(&self, query: &Query) -> Result { self.shared_query(query).await } } From 67af897d648fe9476870d3a0ac766c5e105db4bd Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 13:14:05 +0000 Subject: [PATCH 11/38] Address rc --- .../shared/src/price_estimation/trade_verifier.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 5a68c45315..7047c03ffe 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -402,7 +402,7 @@ fn encode_settlement( (TradeKind::Legacy(_), OrderKind::Buy) => { vec![query.in_amount.get(), *out_amount] } - (TradeKind::Regular(trade), OrderKind::Sell) => { + (TradeKind::Regular(trade), _) => { vec![ *trade .clearing_prices @@ -414,18 +414,6 @@ fn encode_settlement( .context("buy token clearing price is missing")?, ] } - (TradeKind::Regular(trade), OrderKind::Buy) => { - vec![ - *trade - .clearing_prices - .get(&query.buy_token) - .context("buy token clearing price is missing")?, - *trade - .clearing_prices - .get(&query.sell_token) - .context("sell token clearing price is missing")?, - ] - } }; // Configure the most disadvantageous trade possible (while taking possible From 1ee64ba5838de78946cd7cd0777cc5e80af004c1 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 28 Oct 2024 20:13:31 +0000 Subject: [PATCH 12/38] Compute expected amounts --- .../src/price_estimation/trade_verifier.rs | 47 ++++++++++------ crates/shared/src/trade_finding.rs | 54 ++++++++++++++++++- 2 files changed, 84 insertions(+), 17 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 14661e4872..59019185fe 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -111,6 +111,7 @@ impl TradeVerifier { self.native_token, &self.domain_separator, )?; + let settlement = add_balance_queries( settlement, query, @@ -245,12 +246,7 @@ impl TradeVerifier { "verified quote", ); - ensure_quote_accuracy( - &self.quote_inaccuracy_limit, - query, - trade.solver(), - &summary, - ) + ensure_quote_accuracy(&self.quote_inaccuracy_limit, query, trade, &summary) } /// Configures all the state overrides that are needed to mock the given @@ -633,7 +629,7 @@ impl SettleOutput { fn ensure_quote_accuracy( inaccuracy_limit: &BigRational, query: &PriceQuery, - solver: H160, + trade: &TradeKind, summary: &SettleOutput, ) -> Result { // amounts verified by the simulation @@ -642,8 +638,26 @@ fn ensure_quote_accuracy( OrderKind::Sell => (query.in_amount.get(), summary.out_amount), }; - if summary.sell_tokens_lost >= inaccuracy_limit * u256_to_big_rational(&sell_amount) - || summary.buy_tokens_lost >= inaccuracy_limit * u256_to_big_rational(&buy_amount) + let (expected_sell_token_lost, expected_buy_token_lost) = match trade { + TradeKind::Regular(trade) => { + let jit_orders_executed_amounts = trade.jit_orders_executed_amounts()?; + let expected_sell_token_lost = sell_amount + - jit_orders_executed_amounts + .get(&query.sell_token) + .context("missing jit orders sell token executed amount")?; + let expected_buy_token_lost = buy_amount + - jit_orders_executed_amounts + .get(&query.buy_token) + .context("missing jit orders buy token executed amount")?; + (expected_sell_token_lost, expected_buy_token_lost) + } + TradeKind::Legacy(_) => (sell_amount, buy_amount), + }; + + if summary.sell_tokens_lost + >= inaccuracy_limit * u256_to_big_rational(&expected_sell_token_lost) + || summary.buy_tokens_lost + >= inaccuracy_limit * u256_to_big_rational(&expected_buy_token_lost) { return Err(Error::TooInaccurate); } @@ -651,7 +665,7 @@ fn ensure_quote_accuracy( Ok(Estimate { out_amount: summary.out_amount, gas: summary.gas_used.as_u64(), - solver, + solver: trade.solver(), verified: true, }) } @@ -701,11 +715,12 @@ mod tests { sell_tokens_lost: BigRational::from_integer(500.into()), }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_more); + let trade = TradeKind::Legacy(Default::default()); + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &sell_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &sell_more); + let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &sell_more); assert!(estimate.is_ok()); let pay_out_more = SettleOutput { @@ -715,11 +730,11 @@ mod tests { sell_tokens_lost: BigRational::from_integer(0.into()), }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_more); + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &pay_out_more); + let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &pay_out_more); assert!(estimate.is_ok()); let sell_less = SettleOutput { @@ -729,7 +744,7 @@ mod tests { sell_tokens_lost: BigRational::from_integer((-500).into()), }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_less); + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &sell_less); assert!(estimate.is_ok()); let pay_out_less = SettleOutput { @@ -739,7 +754,7 @@ mod tests { sell_tokens_lost: BigRational::from_integer(0.into()), }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_less); + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_less); assert!(estimate.is_ok()); } } diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 3aad80630a..4fee8ad264 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -7,7 +7,7 @@ use { crate::{ conversions::U256Ext, price_estimation::{PriceEstimationError, Query}, - trade_finding::external::dto, + trade_finding::external::{dto, dto::Side}, }, anyhow::{Context, Result}, derivative::Derivative, @@ -170,6 +170,58 @@ impl Trade { big_rational_to_u256(&out_amount).context("out amount is not a valid U256") } + + pub fn jit_orders_executed_amounts(&self) -> Result> { + let mut executed_amounts: HashMap = HashMap::new(); + + for jit_order in self.jit_orders.iter() { + let sell_price = self + .clearing_prices + .get(&jit_order.sell_token) + .context("JIT order sell token clearing price is missing")? + .to_big_rational(); + let buy_price = self + .clearing_prices + .get(&jit_order.buy_token) + .context("JIT order buy token clearing price is missing")? + .to_big_rational(); + let executed_amount = jit_order.executed_amount.to_big_rational(); + let (executed_sell, executed_buy) = match jit_order.side { + Side::Sell => { + let buy_amount = executed_amount + .clone() + .mul(&sell_price) + .checked_div(&buy_price) + .context("division by zero in JIT order sell")?; + (executed_amount, buy_amount) + } + Side::Buy => { + let sell_amount = executed_amount + .clone() + .mul(&buy_price) + .checked_div(&sell_price) + .context("division by zero in JIT order buy")?; + (sell_amount, executed_amount) + } + }; + let (executed_sell, executed_buy) = ( + big_rational_to_u256(&executed_sell) + .context("executed sell amount is not a valid U256")?, + big_rational_to_u256(&executed_buy) + .context("executed buy amount is not a valid U256")?, + ); + executed_amounts + .entry(jit_order.sell_token) + .and_modify(|executed| *executed += executed_sell) + .or_insert(executed_sell); + executed_amounts + .entry(jit_order.buy_token) + .and_modify(|executed| *executed += executed_buy) + .or_insert(executed_buy); + } + + Ok(executed_amounts) + } } /// Data for a raw GPv2 interaction. From f676a96ea8a70ca77ac8643754b225b17e609bf0 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 17:22:57 +0000 Subject: [PATCH 13/38] Compute jit orders net changes --- crates/e2e/tests/e2e/quote_verification.rs | 4 +- crates/number/src/conversions.rs | 110 +++- crates/shared/src/price_estimation.rs | 4 +- crates/shared/src/price_estimation/factory.rs | 2 +- .../src/price_estimation/trade_verifier.rs | 493 +++++++++++++++++- crates/shared/src/trade_finding.rs | 33 +- 6 files changed, 604 insertions(+), 42 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index d12f249ab3..f20ad1a63d 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -6,7 +6,7 @@ use { order::{BuyTokenDestination, OrderKind, SellTokenSource}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, }, - number::nonzero::U256 as NonZeroU256, + number::{conversions::big_rational_from_decimal_str, nonzero::U256 as NonZeroU256}, shared::{ price_estimation::{ trade_verifier::{PriceQuery, TradeVerifier, TradeVerifying}, @@ -61,7 +61,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { block_stream, onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), - 0.0, + big_rational_from_decimal_str("0.0").unwrap(), ) .await .unwrap(); diff --git a/crates/number/src/conversions.rs b/crates/number/src/conversions.rs index 5283a86cbd..8d0c013945 100644 --- a/crates/number/src/conversions.rs +++ b/crates/number/src/conversions.rs @@ -1,8 +1,9 @@ use { - anyhow::{ensure, Result}, + anyhow::{ensure, Context, Result}, bigdecimal::{num_bigint::ToBigInt, BigDecimal}, num::{bigint::Sign, rational::Ratio, BigInt, BigRational, BigUint, Zero}, primitive_types::U256, + std::str::FromStr, }; pub fn u256_to_big_uint(input: &U256) -> BigUint { @@ -80,6 +81,57 @@ pub fn big_decimal_to_big_rational(value: &BigDecimal) -> BigRational { BigRational::new(adjusted_numer, denom) } +/// Floats in Rust have wrong bytes representation which leaks into +/// `BigRational` instance when converting from float. +/// +/// This function converts a decimal string (e.g., `"0.1"`) to an exact +/// `BigRational`. +pub fn big_rational_from_decimal_str(s: &str) -> Result { + let s = s.trim(); + // Handle negative numbers + let (is_negative, s) = if let Some(stripped) = s.strip_prefix('-') { + (true, stripped) + } else { + (false, s) + }; + + let parts: Vec<&str> = s.split('.').collect(); + let unsigned_rational = match parts.len() { + 1 => { + // No fractional part + let numerator = BigInt::from_str(parts[0]).context("unable to parse integer part")?; + Ok(BigRational::from_integer(numerator)) + } + 2 => { + let integer_part = if parts[0].is_empty() { + // Handle cases like ".5" as "0.5" + BigInt::zero() + } else { + BigInt::from_str(parts[0]).context("unable to parse integer part")? + }; + + let fractional_part = if parts[1].is_empty() { + // Handle cases like "1." as "1.0" + BigInt::zero() + } else { + BigInt::from_str(parts[1]).context("unable to parse fractional part")? + }; + + let fractional_length = parts[1].len() as u32; + + let denominator = BigInt::from(10u32).pow(fractional_length); + let numerator = integer_part + .checked_mul(&denominator) + .context("rational overflow during multiplication")? + .checked_add(&fractional_part) + .context("rational overflow during addition")?; + Ok(BigRational::new(numerator, denominator)) + } + _ => Err(anyhow::anyhow!("invalid decimal number")), + }; + unsigned_rational.map(|rational| if is_negative { -rational } else { rational }) +} + #[cfg(test)] mod tests { use {super::*, num::One, std::str::FromStr}; @@ -215,4 +267,60 @@ mod tests { ); assert!(big_decimal_to_u256(&(max_u256_as_big_decimal + BigDecimal::one())).is_none()); } + + #[test] + fn big_rational_from_decimal_str_() { + assert_eq!( + big_rational_from_decimal_str("0").unwrap(), + BigRational::zero() + ); + assert_eq!( + big_rational_from_decimal_str("1").unwrap(), + BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("-1").unwrap(), + -BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("1.0").unwrap(), + BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("1.").unwrap(), + BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("-1.").unwrap(), + -BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("1.000").unwrap(), + BigRational::one() + ); + assert_eq!( + big_rational_from_decimal_str("0.1").unwrap(), + BigRational::new(1.into(), 10.into()) + ); + assert_eq!( + big_rational_from_decimal_str(".1").unwrap(), + BigRational::new(1.into(), 10.into()) + ); + assert_eq!( + big_rational_from_decimal_str("-.1").unwrap(), + -BigRational::new(1.into(), 10.into()) + ); + assert_eq!( + big_rational_from_decimal_str("0.125").unwrap(), + BigRational::new(1.into(), 8.into()) + ); + assert_eq!( + big_rational_from_decimal_str("-0.125").unwrap(), + -BigRational::new(1.into(), 8.into()) + ); + + assert!(big_rational_from_decimal_str("0.1.0").is_err()); + assert!(big_rational_from_decimal_str("a").is_err()); + assert!(big_rational_from_decimal_str("1 0").is_err()); + } } diff --git a/crates/shared/src/price_estimation.rs b/crates/shared/src/price_estimation.rs index 6a8fa88674..27fe0fb2ba 100644 --- a/crates/shared/src/price_estimation.rs +++ b/crates/shared/src/price_estimation.rs @@ -191,8 +191,8 @@ pub struct Arguments { /// factor. /// E.g. a value of `0.01` means at most 1 percent of the sell or buy tokens /// can be paid out of the settlement contract buffers. - #[clap(long, env, default_value = "1.")] - pub quote_inaccuracy_limit: f64, + #[clap(long, env, default_value = "1.", value_parser = number::conversions::big_rational_from_decimal_str)] + pub quote_inaccuracy_limit: BigRational, /// How strict quote verification should be. #[clap( diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index c3fc78983c..81598c8b85 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -122,7 +122,7 @@ impl<'a> PriceEstimatorFactory<'a> { network.block_stream.clone(), network.settlement, network.native_token, - args.quote_inaccuracy_limit, + args.quote_inaccuracy_limit.clone(), ) .await?, ))) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 59019185fe..45d531f1d8 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -8,6 +8,7 @@ use { trade_finding::{external::dto, Interaction, TradeKind}, }, anyhow::{Context, Result}, + bigdecimal::{Signed, Zero}, contracts::{ deployed_bytecode, dummy_contract, @@ -66,7 +67,7 @@ impl TradeVerifier { block_stream: CurrentBlockWatcher, settlement: H160, native_token: H160, - quote_inaccuracy_limit: f64, + quote_inaccuracy_limit: BigRational, ) -> Result { let settlement_contract = GPv2Settlement::at(&web3, settlement); let domain_separator = @@ -77,8 +78,7 @@ impl TradeVerifier { block_stream, settlement: settlement_contract, native_token, - quote_inaccuracy_limit: BigRational::from_float(quote_inaccuracy_limit) - .expect("can represent all finite values"), + quote_inaccuracy_limit, web3, domain_separator, }) @@ -631,34 +631,41 @@ fn ensure_quote_accuracy( query: &PriceQuery, trade: &TradeKind, summary: &SettleOutput, -) -> Result { +) -> std::result::Result { // amounts verified by the simulation let (sell_amount, buy_amount) = match query.kind { OrderKind::Buy => (summary.out_amount, query.in_amount.get()), OrderKind::Sell => (query.in_amount.get(), summary.out_amount), }; + let (sell_amount, buy_amount) = ( + u256_to_big_rational(&sell_amount), + u256_to_big_rational(&buy_amount), + ); let (expected_sell_token_lost, expected_buy_token_lost) = match trade { TradeKind::Regular(trade) => { - let jit_orders_executed_amounts = trade.jit_orders_executed_amounts()?; - let expected_sell_token_lost = sell_amount - - jit_orders_executed_amounts - .get(&query.sell_token) - .context("missing jit orders sell token executed amount")?; - let expected_buy_token_lost = buy_amount - - jit_orders_executed_amounts + let jit_orders_net_token_changes = trade.jit_orders_net_token_changes()?; + let jit_orders_sell_token_changes = jit_orders_net_token_changes + .get(&query.sell_token) + .context("missing jit orders sell token executed amount")?; + let jit_orders_buy_token_changes = + jit_orders_net_token_changes .get(&query.buy_token) .context("missing jit orders buy token executed amount")?; + + let expected_sell_token_lost = -&sell_amount - jit_orders_sell_token_changes; + let expected_buy_token_lost = &buy_amount - jit_orders_buy_token_changes; (expected_sell_token_lost, expected_buy_token_lost) } - TradeKind::Legacy(_) => (sell_amount, buy_amount), + TradeKind::Legacy(_) => (BigRational::zero(), BigRational::zero()), }; - if summary.sell_tokens_lost - >= inaccuracy_limit * u256_to_big_rational(&expected_sell_token_lost) - || summary.buy_tokens_lost - >= inaccuracy_limit * u256_to_big_rational(&expected_buy_token_lost) - { + let sell_token_lost = (&summary.sell_tokens_lost - expected_sell_token_lost).abs(); + let sell_token_lost_limit = inaccuracy_limit * &sell_amount; + let buy_token_lost = (&summary.buy_tokens_lost - expected_buy_token_lost).abs(); + let buy_token_lost_limit = inaccuracy_limit * &buy_amount; + + if sell_token_lost >= sell_token_lost_limit || buy_token_lost >= buy_token_lost_limit { return Err(Error::TooInaccurate); } @@ -692,7 +699,15 @@ enum Error { #[cfg(test)] mod tests { - use super::*; + use { + super::*, + crate::trade_finding::Trade, + app_data::AppDataHash, + bigdecimal::FromPrimitive, + model::order::{BuyTokenDestination, SellTokenSource}, + num::BigInt, + number::conversions::big_rational_from_decimal_str, + }; #[test] fn discards_inaccurate_quotes() { @@ -757,4 +772,446 @@ mod tests { let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_less); assert!(estimate.is_ok()); } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_partial_sell_sell() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to sell 500 units of Token A for Token B + let query = PriceQuery { + in_amount: NonZeroU256::new(500u64.into()).unwrap(), + kind: OrderKind::Sell, + sell_token, + buy_token, + }; + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order partially covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(200u64), + side: dto::Side::Sell, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with zero net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount still covers the full query amount + out_amount: U256::from(250u64), + // Since the JIT order covered only 200 units of Token B, the settlement contract is + // expected to lose the remaining 50 units. Let's lose a bit more. + buy_tokens_lost: BigRational::from_u64(75).unwrap(), + // And the settlement contract is expected to receive 100 units of Token A. Let's gain a + // bit more. + sell_tokens_lost: BigRational::from_i64(-150).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_sell_sell() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to sell 500 units of Token A for Token B + let query = PriceQuery { + in_amount: NonZeroU256::new(500u64.into()).unwrap(), + kind: OrderKind::Sell, + sell_token, + buy_token, + }; + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order fully covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(250u64), + side: dto::Side::Sell, + sell_amount: U256::from(250u64), + buy_amount: U256::from(500u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with zero net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount still covers the full query amount + out_amount: U256::from(250u64), + // Since the JIT order fully covered the requested amount, the settlement contract + // should not have any net changes but we add some withing the 11% limit. + buy_tokens_lost: BigRational::from_u64(25).unwrap(), + sell_tokens_lost: BigRational::from_i64(50).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_partial_buy_buy() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to buy 250 units of Token B using Token A + let query = PriceQuery { + in_amount: NonZeroU256::new(250u64.into()).unwrap(), + kind: OrderKind::Buy, + sell_token, + buy_token, + }; + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order partially covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(400u64), + side: dto::Side::Buy, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount is the total amount of buy token the user gets + out_amount: U256::from(500u64), + // Settlement contract covers the remaining 50 units of Token B, but to test the 11% + // limit we lose a bit more. + buy_tokens_lost: BigRational::from_u64(75).unwrap(), + // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit + // more. + sell_tokens_lost: BigRational::from_i64(-150).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_buy_buy() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to buy 250 units of Token B using Token A + let query = PriceQuery { + in_amount: NonZeroU256::new(250u64.into()).unwrap(), + kind: OrderKind::Buy, + sell_token, + buy_token, + }; + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order fully covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(500u64), + side: dto::Side::Buy, + sell_amount: U256::from(250u64), + buy_amount: U256::from(500u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount is the total amount of buy token the user gets + out_amount: U256::from(500u64), + // Settlement contract balance shouldn't change, but to test the 11% limit we lose a + // bit. + buy_tokens_lost: BigRational::from_u64(25).unwrap(), + // Settlement contract balance shouldn't change, but to test the 11% limit we gain a + // bit. + sell_tokens_lost: BigRational::from_i64(-50).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_partial_buy_sell() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to buy 250 units of Token B using Token A + let query = PriceQuery { + in_amount: NonZeroU256::new(250u64.into()).unwrap(), + kind: OrderKind::Buy, + sell_token, + buy_token, + }; + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order partially covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(200u64), + side: dto::Side::Sell, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount is the total amount of buy token the user gets + out_amount: U256::from(500u64), + // Settlement contract covers the remaining 50 units of Token B, but to test the 11% + // limit we lose a bit more. + buy_tokens_lost: BigRational::from_u64(75).unwrap(), + // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit + // more. + sell_tokens_lost: BigRational::from_i64(-150).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } + + #[test] + fn test_ensure_quote_accuracy_with_jit_orders_partial_sell_buy() { + // Inaccuracy limit of 10% + let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + + let sell_token: H160 = H160::from_low_u64_be(1); + let buy_token: H160 = H160::from_low_u64_be(2); + + // User wants to sell 500 units of Token A for Token B + let query = PriceQuery { + in_amount: NonZeroU256::new(500u64.into()).unwrap(), + kind: OrderKind::Sell, + sell_token, + buy_token, + }; + + assert_eq!( + BigRational::from_u64(50).unwrap(), + BigRational::new(BigInt::from(1), BigInt::from(10)) + * BigRational::from_u64(500).unwrap() + ); + + // Clearing prices: Token A = 1, Token B = 2 + let mut clearing_prices = HashMap::new(); + clearing_prices.insert(sell_token, U256::from(1u64)); + clearing_prices.insert(buy_token, U256::from(2u64)); + + // JIT order partially covers the user's trade + let jit_order = dto::JitOrder { + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(400u64), + side: dto::Side::Buy, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }; + + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order], + }); + + // Simulation summary with net changes for the settlement contract + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + // The out amount is the total amount of buy token the user gets + out_amount: U256::from(250u64), + // Settlement contract covers the remaining 50 units of Token B, but to test the 11% + // limit we lose a bit more. + buy_tokens_lost: BigRational::from_u64(75).unwrap(), + // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit + // more. + sell_tokens_lost: BigRational::from_i64(-150).unwrap(), + }; + + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } } diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 4fee8ad264..3068ad11c5 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -13,7 +13,7 @@ use { derivative::Derivative, ethcontract::{contract::MethodBuilder, tokens::Tokenize, web3::Transport, Bytes, H160, U256}, model::{interaction::InteractionData, order::OrderKind}, - num::CheckedDiv, + num::{BigRational, CheckedDiv}, number::conversions::big_rational_to_u256, serde::Serialize, std::{collections::HashMap, ops::Mul}, @@ -171,8 +171,8 @@ impl Trade { big_rational_to_u256(&out_amount).context("out amount is not a valid U256") } - pub fn jit_orders_executed_amounts(&self) -> Result> { - let mut executed_amounts: HashMap = HashMap::new(); + pub fn jit_orders_net_token_changes(&self) -> Result> { + let mut net_token_changes: HashMap = HashMap::new(); for jit_order in self.jit_orders.iter() { let sell_price = self @@ -186,6 +186,7 @@ impl Trade { .context("JIT order buy token clearing price is missing")? .to_big_rational(); let executed_amount = jit_order.executed_amount.to_big_rational(); + let (executed_sell, executed_buy) = match jit_order.side { Side::Sell => { let buy_amount = executed_amount @@ -193,7 +194,7 @@ impl Trade { .mul(&sell_price) .checked_div(&buy_price) .context("division by zero in JIT order sell")?; - (executed_amount, buy_amount) + (executed_amount.clone(), buy_amount) } Side::Buy => { let sell_amount = executed_amount @@ -201,26 +202,22 @@ impl Trade { .mul(&buy_price) .checked_div(&sell_price) .context("division by zero in JIT order buy")?; - (sell_amount, executed_amount) + (sell_amount, executed_amount.clone()) } }; - let (executed_sell, executed_buy) = ( - big_rational_to_u256(&executed_sell) - .context("executed sell amount is not a valid U256")?, - big_rational_to_u256(&executed_buy) - .context("executed buy amount is not a valid U256")?, - ); - executed_amounts + + net_token_changes .entry(jit_order.sell_token) - .and_modify(|executed| *executed += executed_sell) - .or_insert(executed_sell); - executed_amounts + .and_modify(|e| *e += executed_sell.clone()) + .or_insert(executed_sell.clone()); + + net_token_changes .entry(jit_order.buy_token) - .and_modify(|executed| *executed += executed_buy) - .or_insert(executed_buy); + .and_modify(|e| *e -= executed_buy.clone()) + .or_insert(-executed_buy.clone()); } - Ok(executed_amounts) + Ok(net_token_changes) } } From 941647c495a4fb9c9aa3746fc5c0e4bda48a6ec4 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 18:02:19 +0000 Subject: [PATCH 14/38] Combine tests --- .../src/price_estimation/trade_verifier.rs | 577 ++++++------------ 1 file changed, 175 insertions(+), 402 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 45d531f1d8..f4c365f0ee 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -705,7 +705,6 @@ mod tests { app_data::AppDataHash, bigdecimal::FromPrimitive, model::order::{BuyTokenDestination, SellTokenSource}, - num::BigInt, number::conversions::big_rational_from_decimal_str, }; @@ -774,7 +773,7 @@ mod tests { } #[test] - fn test_ensure_quote_accuracy_with_jit_orders_partial_sell_sell() { + fn ensure_quote_accuracy_with_jit_orders_partial_fills() { // Inaccuracy limit of 10% let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); @@ -782,289 +781,107 @@ mod tests { let sell_token: H160 = H160::from_low_u64_be(1); let buy_token: H160 = H160::from_low_u64_be(2); - // User wants to sell 500 units of Token A for Token B - let query = PriceQuery { - in_amount: NonZeroU256::new(500u64.into()).unwrap(), - kind: OrderKind::Sell, - sell_token, - buy_token, - }; - // Clearing prices: Token A = 1, Token B = 2 let mut clearing_prices = HashMap::new(); clearing_prices.insert(sell_token, U256::from(1u64)); clearing_prices.insert(buy_token, U256::from(2u64)); - // JIT order partially covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(200u64), - side: dto::Side::Sell, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; - - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with zero net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - // The out amount still covers the full query amount - out_amount: U256::from(250u64), - // Since the JIT order covered only 200 units of Token B, the settlement contract is - // expected to lose the remaining 50 units. Let's lose a bit more. - buy_tokens_lost: BigRational::from_u64(75).unwrap(), - // And the settlement contract is expected to receive 100 units of Token A. Let's gain a - // bit more. - sell_tokens_lost: BigRational::from_i64(-150).unwrap(), - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - - #[test] - fn test_ensure_quote_accuracy_with_jit_orders_sell_sell() { - // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - // User wants to sell 500 units of Token A for Token B - let query = PriceQuery { - in_amount: NonZeroU256::new(500u64.into()).unwrap(), - kind: OrderKind::Sell, - sell_token, - buy_token, - }; - - // Clearing prices: Token A = 1, Token B = 2 - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); - - // JIT order fully covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(250u64), - side: dto::Side::Sell, - sell_amount: U256::from(250u64), - buy_amount: U256::from(500u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; - - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with zero net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - // The out amount still covers the full query amount - out_amount: U256::from(250u64), - // Since the JIT order fully covered the requested amount, the settlement contract - // should not have any net changes but we add some withing the 11% limit. - buy_tokens_lost: BigRational::from_u64(25).unwrap(), - sell_tokens_lost: BigRational::from_i64(50).unwrap(), - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - - #[test] - fn test_ensure_quote_accuracy_with_jit_orders_partial_buy_buy() { - // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - // User wants to buy 250 units of Token B using Token A - let query = PriceQuery { - in_amount: NonZeroU256::new(250u64.into()).unwrap(), - kind: OrderKind::Buy, - sell_token, - buy_token, - }; - - // Clearing prices: Token A = 1, Token B = 2 - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); + let queries = [ + PriceQuery { + in_amount: NonZeroU256::new(500u64.into()).unwrap(), + kind: OrderKind::Sell, + sell_token, + buy_token, + }, + PriceQuery { + in_amount: NonZeroU256::new(250u64.into()).unwrap(), + kind: OrderKind::Buy, + sell_token, + buy_token, + }, + ]; + + let jit_orders = [ + dto::JitOrder { + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(200u64), + side: dto::Side::Sell, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }, + dto::JitOrder { + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(400u64), + side: dto::Side::Buy, + sell_amount: U256::from(200u64), + buy_amount: U256::from(400u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }, + ]; - // JIT order partially covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(400u64), - side: dto::Side::Buy, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; + for (query, jit_order) in queries + .iter() + .flat_map(|query| jit_orders.iter().map(move |jit_order| (query, jit_order))) + { + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order.clone()], + }); - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), // The out amount is the total amount of buy token the user gets - out_amount: U256::from(500u64), - // Settlement contract covers the remaining 50 units of Token B, but to test the 11% - // limit we lose a bit more. - buy_tokens_lost: BigRational::from_u64(75).unwrap(), - // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit - // more. - sell_tokens_lost: BigRational::from_i64(-150).unwrap(), - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - - #[test] - fn test_ensure_quote_accuracy_with_jit_orders_buy_buy() { - // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - // User wants to buy 250 units of Token B using Token A - let query = PriceQuery { - in_amount: NonZeroU256::new(250u64.into()).unwrap(), - kind: OrderKind::Buy, - sell_token, - buy_token, - }; - - // Clearing prices: Token A = 1, Token B = 2 - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); - - // JIT order fully covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(500u64), - side: dto::Side::Buy, - sell_amount: U256::from(250u64), - buy_amount: U256::from(500u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; + let out_amount = match (&query.kind, &jit_order.side) { + (OrderKind::Sell, dto::Side::Sell) => U256::from(250u64), + (OrderKind::Buy, dto::Side::Buy) => U256::from(500u64), + (OrderKind::Sell, dto::Side::Buy) => U256::from(250u64), + (OrderKind::Buy, dto::Side::Sell) => U256::from(500u64), + }; - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - // The out amount is the total amount of buy token the user gets - out_amount: U256::from(500u64), - // Settlement contract balance shouldn't change, but to test the 11% limit we lose a - // bit. - buy_tokens_lost: BigRational::from_u64(25).unwrap(), - // Settlement contract balance shouldn't change, but to test the 11% limit we gain a - // bit. - sell_tokens_lost: BigRational::from_i64(-50).unwrap(), - }; + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + out_amount, + // Settlement contract covers the remaining 50 units of Token B, but to test the 11% + // limit we lose a bit more. + buy_tokens_lost: BigRational::from_u64(75).unwrap(), + // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a + // bit more. + sell_tokens_lost: BigRational::from_i64(-150).unwrap(), + }; - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } } #[test] - fn test_ensure_quote_accuracy_with_jit_orders_partial_buy_sell() { + fn ensure_quote_accuracy_with_jit_orders_fully_fills() { // Inaccuracy limit of 10% let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); @@ -1072,146 +889,102 @@ mod tests { let sell_token: H160 = H160::from_low_u64_be(1); let buy_token: H160 = H160::from_low_u64_be(2); - // User wants to buy 250 units of Token B using Token A - let query = PriceQuery { - in_amount: NonZeroU256::new(250u64.into()).unwrap(), - kind: OrderKind::Buy, - sell_token, - buy_token, - }; - // Clearing prices: Token A = 1, Token B = 2 let mut clearing_prices = HashMap::new(); clearing_prices.insert(sell_token, U256::from(1u64)); clearing_prices.insert(buy_token, U256::from(2u64)); - // JIT order partially covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(200u64), - side: dto::Side::Sell, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; - - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - // The out amount is the total amount of buy token the user gets - out_amount: U256::from(500u64), - // Settlement contract covers the remaining 50 units of Token B, but to test the 11% - // limit we lose a bit more. - buy_tokens_lost: BigRational::from_u64(75).unwrap(), - // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit - // more. - sell_tokens_lost: BigRational::from_i64(-150).unwrap(), - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - - #[test] - fn test_ensure_quote_accuracy_with_jit_orders_partial_sell_buy() { - // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - // User wants to sell 500 units of Token A for Token B - let query = PriceQuery { - in_amount: NonZeroU256::new(500u64.into()).unwrap(), - kind: OrderKind::Sell, - sell_token, - buy_token, - }; - - assert_eq!( - BigRational::from_u64(50).unwrap(), - BigRational::new(BigInt::from(1), BigInt::from(10)) - * BigRational::from_u64(500).unwrap() - ); - - // Clearing prices: Token A = 1, Token B = 2 - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); + let queries = [ + PriceQuery { + in_amount: NonZeroU256::new(500u64.into()).unwrap(), + kind: OrderKind::Sell, + sell_token, + buy_token, + }, + PriceQuery { + in_amount: NonZeroU256::new(250u64.into()).unwrap(), + kind: OrderKind::Buy, + sell_token, + buy_token, + }, + ]; + + let jit_orders = [ + dto::JitOrder { + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(200u64), + side: dto::Side::Sell, + sell_amount: U256::from(250u64), + buy_amount: U256::from(500u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }, + dto::JitOrder { + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(500u64), + side: dto::Side::Buy, + sell_amount: U256::from(250u64), + buy_amount: U256::from(500u64), + receiver: H160::zero(), + valid_to: 0, + app_data: AppDataHash::default(), + partially_fillable: false, + sell_token_source: SellTokenSource::Erc20, + buy_token_destination: BuyTokenDestination::Erc20, + signature: vec![], + signing_scheme: SigningScheme::Eip1271, + }, + ]; - // JIT order partially covers the user's trade - let jit_order = dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(400u64), - side: dto::Side::Buy, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }; + for (query, jit_order) in queries + .iter() + .flat_map(|query| jit_orders.iter().map(move |jit_order| (query, jit_order))) + { + let trade_kind = TradeKind::Regular(Trade { + clearing_prices: clearing_prices.clone(), + gas_estimate: Some(50_000), + pre_interactions: vec![], + interactions: vec![], + solver: H160::from_low_u64_be(0x1234), + tx_origin: None, + jit_orders: vec![jit_order.clone()], + }); - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order], - }); - - // Simulation summary with net changes for the settlement contract - let summary = SettleOutput { - gas_used: U256::from(50_000u64), // The out amount is the total amount of buy token the user gets - out_amount: U256::from(250u64), - // Settlement contract covers the remaining 50 units of Token B, but to test the 11% - // limit we lose a bit more. - buy_tokens_lost: BigRational::from_u64(75).unwrap(), - // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a bit - // more. - sell_tokens_lost: BigRational::from_i64(-150).unwrap(), - }; + let out_amount = match (&query.kind, &jit_order.side) { + (OrderKind::Sell, dto::Side::Sell) => U256::from(250u64), + (OrderKind::Buy, dto::Side::Buy) => U256::from(500u64), + (OrderKind::Sell, dto::Side::Buy) => U256::from(250u64), + (OrderKind::Buy, dto::Side::Sell) => U256::from(500u64), + }; - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); + let summary = SettleOutput { + gas_used: U256::from(50_000u64), + out_amount, + // Settlement is not expected to lose any buy tokens, but we lose a bit more to test + // the inaccuracy limit. + buy_tokens_lost: BigRational::from_u64(25).unwrap(), + // Settlement is not expected to gain any sell tokens, but we gain a bit more to + // test the inaccuracy limit. + sell_tokens_lost: BigRational::from_i64(-50).unwrap(), + }; - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); + // The summary has 10% inaccuracy + let estimate = ensure_quote_accuracy(&low_threshold, query, &trade_kind, &summary); + assert!(matches!(estimate, Err(Error::TooInaccurate))); + + // The summary has less than 11% inaccuracy + let estimate = + ensure_quote_accuracy(&high_threshold, query, &trade_kind, &summary).unwrap(); + assert!(estimate.verified); + } } } From 9d522d22ad595b896dee0a8845b11da4ae94c078 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 18:05:40 +0000 Subject: [PATCH 15/38] Avoid redundant clone --- crates/shared/src/trade_finding.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 3068ad11c5..7d6341d78c 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -189,20 +189,18 @@ impl Trade { let (executed_sell, executed_buy) = match jit_order.side { Side::Sell => { - let buy_amount = executed_amount - .clone() + let buy_amount = (&executed_amount) .mul(&sell_price) .checked_div(&buy_price) .context("division by zero in JIT order sell")?; - (executed_amount.clone(), buy_amount) + (executed_amount, buy_amount) } Side::Buy => { - let sell_amount = executed_amount - .clone() + let sell_amount = (&executed_amount) .mul(&buy_price) .checked_div(&sell_price) .context("division by zero in JIT order buy")?; - (sell_amount, executed_amount.clone()) + (sell_amount, executed_amount) } }; From 81836ec88785787d59d290d6c41a8299fc20a26d Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 18:26:41 +0000 Subject: [PATCH 16/38] Use checked_ceil_div --- crates/shared/src/trade_finding.rs | 44 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 7d6341d78c..f0d1bc8696 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -175,44 +175,50 @@ impl Trade { let mut net_token_changes: HashMap = HashMap::new(); for jit_order in self.jit_orders.iter() { - let sell_price = self + let sell_price = *self .clearing_prices .get(&jit_order.sell_token) - .context("JIT order sell token clearing price is missing")? - .to_big_rational(); - let buy_price = self + .context("JIT order sell token clearing price is missing")?; + let buy_price = *self .clearing_prices .get(&jit_order.buy_token) - .context("JIT order buy token clearing price is missing")? - .to_big_rational(); - let executed_amount = jit_order.executed_amount.to_big_rational(); + .context("JIT order buy token clearing price is missing")?; + let executed_amount = jit_order.executed_amount; let (executed_sell, executed_buy) = match jit_order.side { Side::Sell => { - let buy_amount = (&executed_amount) - .mul(&sell_price) - .checked_div(&buy_price) + let buy_amount = executed_amount + .checked_mul(sell_price) + .context("multiplication overflow in JIT order sell")? + .checked_ceil_div(&buy_price) .context("division by zero in JIT order sell")?; - (executed_amount, buy_amount) + ( + executed_amount.to_big_rational(), + buy_amount.to_big_rational(), + ) } Side::Buy => { - let sell_amount = (&executed_amount) - .mul(&buy_price) - .checked_div(&sell_price) + let sell_amount = executed_amount + .checked_mul(buy_price) + .context("multiplication overflow in JIT order buy")? + .checked_ceil_div(&sell_price) .context("division by zero in JIT order buy")?; - (sell_amount, executed_amount) + ( + sell_amount.to_big_rational(), + executed_amount.to_big_rational(), + ) } }; net_token_changes .entry(jit_order.sell_token) - .and_modify(|e| *e += executed_sell.clone()) - .or_insert(executed_sell.clone()); + .and_modify(|e| *e += &executed_sell) + .or_insert(executed_sell); net_token_changes .entry(jit_order.buy_token) - .and_modify(|e| *e -= executed_buy.clone()) - .or_insert(-executed_buy.clone()); + .and_modify(|e| *e -= &executed_buy) + .or_insert(-executed_buy); } Ok(net_token_changes) From a37b3acd29eaa08e73e29f52ef28be76f85b3d5e Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 18:38:01 +0000 Subject: [PATCH 17/38] Redundant modulus --- crates/shared/src/price_estimation/trade_verifier.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index f4c365f0ee..54d1e9e02d 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -8,7 +8,7 @@ use { trade_finding::{external::dto, Interaction, TradeKind}, }, anyhow::{Context, Result}, - bigdecimal::{Signed, Zero}, + bigdecimal::Zero, contracts::{ deployed_bytecode, dummy_contract, @@ -660,9 +660,9 @@ fn ensure_quote_accuracy( TradeKind::Legacy(_) => (BigRational::zero(), BigRational::zero()), }; - let sell_token_lost = (&summary.sell_tokens_lost - expected_sell_token_lost).abs(); + let sell_token_lost = &summary.sell_tokens_lost - expected_sell_token_lost; let sell_token_lost_limit = inaccuracy_limit * &sell_amount; - let buy_token_lost = (&summary.buy_tokens_lost - expected_buy_token_lost).abs(); + let buy_token_lost = &summary.buy_tokens_lost - expected_buy_token_lost; let buy_token_lost_limit = inaccuracy_limit * &buy_amount; if sell_token_lost >= sell_token_lost_limit || buy_token_lost >= buy_token_lost_limit { @@ -712,8 +712,8 @@ mod tests { fn discards_inaccurate_quotes() { // let's use 0.5 as the base case to avoid rounding issues introduced by float // conversion - let low_threshold = BigRational::from_float(0.5).unwrap(); - let high_threshold = BigRational::from_float(0.51).unwrap(); + let low_threshold = big_rational_from_decimal_str("0.5").unwrap(); + let high_threshold = big_rational_from_decimal_str("0.51").unwrap(); let query = PriceQuery { in_amount: 1_000.try_into().unwrap(), From 265e59816931bbb1682c7a6ecfc83226b9cc9488 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 19:17:39 +0000 Subject: [PATCH 18/38] Comment --- crates/shared/src/trade_finding.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index f0d1bc8696..0c7f793800 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -171,6 +171,9 @@ impl Trade { big_rational_to_u256(&out_amount).context("out amount is not a valid U256") } + /// Calculate the net token changes for all the JIT orders. + /// Positive values mean the solver gains the token, negative values mean + /// the solver loses it. pub fn jit_orders_net_token_changes(&self) -> Result> { let mut net_token_changes: HashMap = HashMap::new(); From ac2dbd48040ffdcf1e8f7f315ffa0446cc3fad5e Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 19:45:46 +0000 Subject: [PATCH 19/38] Avoid tokens and prices duplicates --- .../src/price_estimation/trade_verifier.rs | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 54d1e9e02d..de3d09a3a6 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -395,25 +395,23 @@ fn encode_settlement( tracing::trace!("adding unwrap interaction for paying out ETH"); } - let mut tokens = vec![query.sell_token, query.buy_token]; - let mut clearing_prices = match (trade, query.kind) { - (TradeKind::Legacy(_), OrderKind::Sell) => { - vec![*out_amount, query.in_amount.get()] - } - (TradeKind::Legacy(_), OrderKind::Buy) => { - vec![query.in_amount.get(), *out_amount] + let (tokens, clearing_prices) = match trade { + TradeKind::Legacy(_) => { + let tokens = vec![query.sell_token, query.buy_token]; + let prices = match query.kind { + OrderKind::Sell => vec![*out_amount, query.in_amount.get()], + OrderKind::Buy => vec![query.in_amount.get(), *out_amount], + }; + (tokens, prices) } - (TradeKind::Regular(trade), _) => { - vec![ - *trade - .clearing_prices - .get(&query.sell_token) - .context("sell token clearing price is missing")?, - *trade - .clearing_prices - .get(&query.buy_token) - .context("buy token clearing price is missing")?, - ] + TradeKind::Regular(trade) => { + let mut tokens = Vec::with_capacity(trade.clearing_prices.len()); + let mut prices = Vec::with_capacity(trade.clearing_prices.len()); + for (token, price) in trade.clearing_prices.iter() { + tokens.push(*token); + prices.push(*price); + } + (tokens, prices) } }; @@ -448,8 +446,15 @@ fn encode_settlement( &fake_order, &fake_signature, verification.from, - 0, - 1, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &query.sell_token) + .context("missing sell token index")?, + tokens + .iter() + .position(|token| token == &query.buy_token) + .context("missing buy token index")?, &query.in_amount.get(), ); @@ -498,27 +503,19 @@ fn encode_settlement( } }; - tokens.push(jit_order.sell_token); - tokens.push(jit_order.buy_token); - clearing_prices.push( - *trade - .clearing_prices - .get(&jit_order.sell_token) - .context("jit order sell token clearing price is missing")?, - ); - clearing_prices.push( - *trade - .clearing_prices - .get(&jit_order.buy_token) - .context("jit order buy token clearing price is missing")?, - ); - trades.push(encode_trade( &order_data, &signature, owner, - tokens.len() - 2, - tokens.len() - 1, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &jit_order.sell_token) + .context("missing jit order sell token index")?, + tokens + .iter() + .position(|token| token == &jit_order.buy_token) + .context("missing jit order buy token index")?, &jit_order.executed_amount, )); } From 8c7503acc4b57f02e2a02db7a744baec37e37bcf Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 30 Oct 2024 19:55:59 +0000 Subject: [PATCH 20/38] Tests comment --- .../src/price_estimation/trade_verifier.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index de3d09a3a6..b7b954ef25 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -800,9 +800,9 @@ mod tests { let jit_orders = [ dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(200u64), + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(200u64), // Fills the query partially side: dto::Side::Sell, sell_amount: U256::from(200u64), buy_amount: U256::from(400u64), @@ -816,9 +816,9 @@ mod tests { signing_scheme: SigningScheme::Eip1271, }, dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(400u64), + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(400u64), // Fills the query partially side: dto::Side::Buy, sell_amount: U256::from(200u64), buy_amount: U256::from(400u64), @@ -908,9 +908,9 @@ mod tests { let jit_orders = [ dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(200u64), + sell_token: buy_token, // Solver sells Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(250u64), // Fully fills the query side: dto::Side::Sell, sell_amount: U256::from(250u64), buy_amount: U256::from(500u64), @@ -924,9 +924,9 @@ mod tests { signing_scheme: SigningScheme::Eip1271, }, dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(500u64), + sell_token: buy_token, // Solver sell Token B + buy_token: sell_token, // Solver buys Token A + executed_amount: U256::from(500u64), // Fully fills the query side: dto::Side::Buy, sell_amount: U256::from(250u64), buy_amount: U256::from(500u64), From e68e0df12260e4ef118305408c4e524de18246e9 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 31 Oct 2024 09:59:24 +0000 Subject: [PATCH 21/38] Encode jit orders function --- .../src/price_estimation/trade_verifier.rs | 135 ++++++++++-------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index b7b954ef25..72b263f6a5 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -3,7 +3,7 @@ use { crate::{ code_fetching::CodeFetching, code_simulation::CodeSimulating, - encoded_settlement::{encode_trade, EncodedSettlement}, + encoded_settlement::{encode_trade, EncodedSettlement, EncodedTrade}, interaction::EncodedInteraction, trade_finding::{external::dto, Interaction, TradeKind}, }, @@ -459,66 +459,12 @@ fn encode_settlement( ); let mut trades = vec![encoded_trade]; - if let TradeKind::Regular(trade) = trade { - for jit_order in trade.jit_orders.iter() { - let order_data = OrderData { - sell_token: jit_order.sell_token, - buy_token: jit_order.buy_token, - receiver: Some(jit_order.receiver), - sell_amount: jit_order.sell_amount, - buy_amount: jit_order.buy_amount, - valid_to: jit_order.valid_to, - app_data: jit_order.app_data, - fee_amount: 0.into(), - kind: match &jit_order.side { - dto::Side::Buy => OrderKind::Buy, - dto::Side::Sell => OrderKind::Sell, - }, - partially_fillable: jit_order.partially_fillable, - sell_token_balance: jit_order.sell_token_source, - buy_token_balance: jit_order.buy_token_destination, - }; - let (owner, signature) = match jit_order.signing_scheme { - SigningScheme::Eip1271 => { - let (owner, signature) = jit_order.signature.split_at(20); - let owner = H160::from_slice(owner); - let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; - (owner, signature) - } - SigningScheme::PreSign => { - let owner = H160::from_slice(&jit_order.signature); - let signature = - Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; - (owner, signature) - } - _ => { - let signature = - Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; - let owner = signature - .recover(domain_separator, &order_data.hash_struct())? - .context("could not recover the owner")? - .signer; - (owner, signature) - } - }; - - trades.push(encode_trade( - &order_data, - &signature, - owner, - // the tokens set length is small so the linear search is acceptable - tokens - .iter() - .position(|token| token == &jit_order.sell_token) - .context("missing jit order sell token index")?, - tokens - .iter() - .position(|token| token == &jit_order.buy_token) - .context("missing jit order buy token index")?, - &jit_order.executed_amount, - )); - } + trades.extend(encode_jit_orders( + &trade.jit_orders, + &tokens, + domain_separator, + )?); } let mut pre_interactions = verification.pre_interactions.clone(); pre_interactions.extend(trade.pre_interactions().iter().cloned()); @@ -535,6 +481,75 @@ fn encode_settlement( }) } +fn encode_jit_orders( + jit_orders: &[dto::JitOrder], + tokens: &[H160], + domain_separator: &DomainSeparator, +) -> Result, Error> { + let mut trades = Vec::with_capacity(jit_orders.len()); + + for jit_order in jit_orders.iter() { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: 0.into(), + kind: match &jit_order.side { + dto::Side::Buy => OrderKind::Buy, + dto::Side::Sell => OrderKind::Sell, + }, + partially_fillable: jit_order.partially_fillable, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = match jit_order.signing_scheme { + SigningScheme::Eip1271 => { + let (owner, signature) = jit_order.signature.split_at(20); + let owner = H160::from_slice(owner); + let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; + (owner, signature) + } + SigningScheme::PreSign => { + let owner = H160::from_slice(&jit_order.signature); + let signature = + Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; + (owner, signature) + } + _ => { + let signature = + Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; + let owner = signature + .recover(domain_separator, &order_data.hash_struct())? + .context("could not recover the owner")? + .signer; + (owner, signature) + } + }; + + trades.push(encode_trade( + &order_data, + &signature, + owner, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &jit_order.sell_token) + .context("missing jit order sell token index")?, + tokens + .iter() + .position(|token| token == &jit_order.buy_token) + .context("missing jit order buy token index")?, + &jit_order.executed_amount, + )); + } + + Ok(trades) +} + /// Adds the interactions that are only needed to query important balances /// throughout the simulation. /// These balances will get used to compute an accurate price for the trade. From 0afb4730c70032e3cd8dee825880d0b6862bd6bb Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 31 Oct 2024 10:07:23 +0000 Subject: [PATCH 22/38] Encode fake trade function --- .../src/price_estimation/trade_verifier.rs | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 72b263f6a5..17efc12ef1 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -415,6 +415,37 @@ fn encode_settlement( } }; + let fake_trade = encode_fake_trade(query, verification, out_amount, &tokens)?; + let mut trades = vec![fake_trade]; + if let TradeKind::Regular(trade) = trade { + trades.extend(encode_jit_orders( + &trade.jit_orders, + &tokens, + domain_separator, + )?); + } + + let mut pre_interactions = verification.pre_interactions.clone(); + pre_interactions.extend(trade.pre_interactions().iter().cloned()); + + Ok(EncodedSettlement { + tokens, + clearing_prices, + trades, + interactions: [ + encode_interactions(&pre_interactions), + trade_interactions, + encode_interactions(&verification.post_interactions), + ], + }) +} + +fn encode_fake_trade( + query: &PriceQuery, + verification: &Verification, + out_amount: &U256, + tokens: &[H160], +) -> Result { // Configure the most disadvantageous trade possible (while taking possible // overflows into account). Should the trader not receive the amount promised by // the [`Trade`] the simulation will still work and we can compute the actual @@ -458,27 +489,7 @@ fn encode_settlement( &query.in_amount.get(), ); - let mut trades = vec![encoded_trade]; - if let TradeKind::Regular(trade) = trade { - trades.extend(encode_jit_orders( - &trade.jit_orders, - &tokens, - domain_separator, - )?); - } - let mut pre_interactions = verification.pre_interactions.clone(); - pre_interactions.extend(trade.pre_interactions().iter().cloned()); - - Ok(EncodedSettlement { - tokens, - clearing_prices, - trades, - interactions: [ - encode_interactions(&pre_interactions), - trade_interactions, - encode_interactions(&verification.post_interactions), - ], - }) + Ok(encoded_trade) } fn encode_jit_orders( From 09e6330cd47d47e8c455e7e08a38447fa99deff0 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 31 Oct 2024 12:13:53 +0000 Subject: [PATCH 23/38] BigRational::neg() --- crates/number/src/conversions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/number/src/conversions.rs b/crates/number/src/conversions.rs index 8d0c013945..45d1afac36 100644 --- a/crates/number/src/conversions.rs +++ b/crates/number/src/conversions.rs @@ -3,7 +3,7 @@ use { bigdecimal::{num_bigint::ToBigInt, BigDecimal}, num::{bigint::Sign, rational::Ratio, BigInt, BigRational, BigUint, Zero}, primitive_types::U256, - std::str::FromStr, + std::{ops::Neg, str::FromStr}, }; pub fn u256_to_big_uint(input: &U256) -> BigUint { @@ -96,7 +96,7 @@ pub fn big_rational_from_decimal_str(s: &str) -> Result { }; let parts: Vec<&str> = s.split('.').collect(); - let unsigned_rational = match parts.len() { + match parts.len() { 1 => { // No fractional part let numerator = BigInt::from_str(parts[0]).context("unable to parse integer part")?; @@ -128,8 +128,8 @@ pub fn big_rational_from_decimal_str(s: &str) -> Result { Ok(BigRational::new(numerator, denominator)) } _ => Err(anyhow::anyhow!("invalid decimal number")), - }; - unsigned_rational.map(|rational| if is_negative { -rational } else { rational }) + } + .map(|ratio| if is_negative { ratio.neg() } else { ratio }) } #[cfg(test)] From 2e71436af0c947f3b382d21830624d89e6461ffa Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 31 Oct 2024 12:32:22 +0000 Subject: [PATCH 24/38] Use BigDecimal in the config --- crates/e2e/tests/e2e/quote_verification.rs | 5 +- crates/number/src/conversions.rs | 110 +----------------- crates/shared/src/price_estimation.rs | 5 +- .../src/price_estimation/trade_verifier.rs | 25 ++-- 4 files changed, 21 insertions(+), 124 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index f20ad1a63d..4b1cfa51bc 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -6,7 +6,7 @@ use { order::{BuyTokenDestination, OrderKind, SellTokenSource}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, }, - number::{conversions::big_rational_from_decimal_str, nonzero::U256 as NonZeroU256}, + number::nonzero::U256 as NonZeroU256, shared::{ price_estimation::{ trade_verifier::{PriceQuery, TradeVerifier, TradeVerifying}, @@ -15,6 +15,7 @@ use { }, trade_finding::{Interaction, LegacyTrade, TradeKind}, }, + sqlx::types::BigDecimal, std::{str::FromStr, sync::Arc}, }; @@ -61,7 +62,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { block_stream, onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), - big_rational_from_decimal_str("0.0").unwrap(), + BigDecimal::from_str("0.0").unwrap(), ) .await .unwrap(); diff --git a/crates/number/src/conversions.rs b/crates/number/src/conversions.rs index 45d1afac36..5283a86cbd 100644 --- a/crates/number/src/conversions.rs +++ b/crates/number/src/conversions.rs @@ -1,9 +1,8 @@ use { - anyhow::{ensure, Context, Result}, + anyhow::{ensure, Result}, bigdecimal::{num_bigint::ToBigInt, BigDecimal}, num::{bigint::Sign, rational::Ratio, BigInt, BigRational, BigUint, Zero}, primitive_types::U256, - std::{ops::Neg, str::FromStr}, }; pub fn u256_to_big_uint(input: &U256) -> BigUint { @@ -81,57 +80,6 @@ pub fn big_decimal_to_big_rational(value: &BigDecimal) -> BigRational { BigRational::new(adjusted_numer, denom) } -/// Floats in Rust have wrong bytes representation which leaks into -/// `BigRational` instance when converting from float. -/// -/// This function converts a decimal string (e.g., `"0.1"`) to an exact -/// `BigRational`. -pub fn big_rational_from_decimal_str(s: &str) -> Result { - let s = s.trim(); - // Handle negative numbers - let (is_negative, s) = if let Some(stripped) = s.strip_prefix('-') { - (true, stripped) - } else { - (false, s) - }; - - let parts: Vec<&str> = s.split('.').collect(); - match parts.len() { - 1 => { - // No fractional part - let numerator = BigInt::from_str(parts[0]).context("unable to parse integer part")?; - Ok(BigRational::from_integer(numerator)) - } - 2 => { - let integer_part = if parts[0].is_empty() { - // Handle cases like ".5" as "0.5" - BigInt::zero() - } else { - BigInt::from_str(parts[0]).context("unable to parse integer part")? - }; - - let fractional_part = if parts[1].is_empty() { - // Handle cases like "1." as "1.0" - BigInt::zero() - } else { - BigInt::from_str(parts[1]).context("unable to parse fractional part")? - }; - - let fractional_length = parts[1].len() as u32; - - let denominator = BigInt::from(10u32).pow(fractional_length); - let numerator = integer_part - .checked_mul(&denominator) - .context("rational overflow during multiplication")? - .checked_add(&fractional_part) - .context("rational overflow during addition")?; - Ok(BigRational::new(numerator, denominator)) - } - _ => Err(anyhow::anyhow!("invalid decimal number")), - } - .map(|ratio| if is_negative { ratio.neg() } else { ratio }) -} - #[cfg(test)] mod tests { use {super::*, num::One, std::str::FromStr}; @@ -267,60 +215,4 @@ mod tests { ); assert!(big_decimal_to_u256(&(max_u256_as_big_decimal + BigDecimal::one())).is_none()); } - - #[test] - fn big_rational_from_decimal_str_() { - assert_eq!( - big_rational_from_decimal_str("0").unwrap(), - BigRational::zero() - ); - assert_eq!( - big_rational_from_decimal_str("1").unwrap(), - BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("-1").unwrap(), - -BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("1.0").unwrap(), - BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("1.").unwrap(), - BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("-1.").unwrap(), - -BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("1.000").unwrap(), - BigRational::one() - ); - assert_eq!( - big_rational_from_decimal_str("0.1").unwrap(), - BigRational::new(1.into(), 10.into()) - ); - assert_eq!( - big_rational_from_decimal_str(".1").unwrap(), - BigRational::new(1.into(), 10.into()) - ); - assert_eq!( - big_rational_from_decimal_str("-.1").unwrap(), - -BigRational::new(1.into(), 10.into()) - ); - assert_eq!( - big_rational_from_decimal_str("0.125").unwrap(), - BigRational::new(1.into(), 8.into()) - ); - assert_eq!( - big_rational_from_decimal_str("-0.125").unwrap(), - -BigRational::new(1.into(), 8.into()) - ); - - assert!(big_rational_from_decimal_str("0.1.0").is_err()); - assert!(big_rational_from_decimal_str("a").is_err()); - assert!(big_rational_from_decimal_str("1 0").is_err()); - } } diff --git a/crates/shared/src/price_estimation.rs b/crates/shared/src/price_estimation.rs index 27fe0fb2ba..8355acef1c 100644 --- a/crates/shared/src/price_estimation.rs +++ b/crates/shared/src/price_estimation.rs @@ -5,6 +5,7 @@ use { trade_finding::Interaction, }, anyhow::Result, + bigdecimal::BigDecimal, ethcontract::{H160, U256}, futures::future::BoxFuture, itertools::Itertools, @@ -191,8 +192,8 @@ pub struct Arguments { /// factor. /// E.g. a value of `0.01` means at most 1 percent of the sell or buy tokens /// can be paid out of the settlement contract buffers. - #[clap(long, env, default_value = "1.", value_parser = number::conversions::big_rational_from_decimal_str)] - pub quote_inaccuracy_limit: BigRational, + #[clap(long, env, default_value = "1.")] + pub quote_inaccuracy_limit: BigDecimal, /// How strict quote verification should be. #[clap( diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 17efc12ef1..2d90b0a90d 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -8,7 +8,7 @@ use { trade_finding::{external::dto, Interaction, TradeKind}, }, anyhow::{Context, Result}, - bigdecimal::Zero, + bigdecimal::{BigDecimal, Zero}, contracts::{ deployed_bytecode, dummy_contract, @@ -25,7 +25,10 @@ use { DomainSeparator, }, num::BigRational, - number::{conversions::u256_to_big_rational, nonzero::U256 as NonZeroU256}, + number::{ + conversions::{big_decimal_to_big_rational, u256_to_big_rational}, + nonzero::U256 as NonZeroU256, + }, std::{collections::HashMap, sync::Arc}, web3::{ethabi::Token, types::CallRequest}, }; @@ -67,7 +70,7 @@ impl TradeVerifier { block_stream: CurrentBlockWatcher, settlement: H160, native_token: H160, - quote_inaccuracy_limit: BigRational, + quote_inaccuracy_limit: BigDecimal, ) -> Result { let settlement_contract = GPv2Settlement::at(&web3, settlement); let domain_separator = @@ -78,7 +81,7 @@ impl TradeVerifier { block_stream, settlement: settlement_contract, native_token, - quote_inaccuracy_limit, + quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), web3, domain_separator, }) @@ -728,15 +731,15 @@ mod tests { app_data::AppDataHash, bigdecimal::FromPrimitive, model::order::{BuyTokenDestination, SellTokenSource}, - number::conversions::big_rational_from_decimal_str, + std::str::FromStr, }; #[test] fn discards_inaccurate_quotes() { // let's use 0.5 as the base case to avoid rounding issues introduced by float // conversion - let low_threshold = big_rational_from_decimal_str("0.5").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.51").unwrap(); + let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.5").unwrap()); + let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.51").unwrap()); let query = PriceQuery { in_amount: 1_000.try_into().unwrap(), @@ -798,8 +801,8 @@ mod tests { #[test] fn ensure_quote_accuracy_with_jit_orders_partial_fills() { // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.1").unwrap()); + let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.11").unwrap()); let sell_token: H160 = H160::from_low_u64_be(1); let buy_token: H160 = H160::from_low_u64_be(2); @@ -906,8 +909,8 @@ mod tests { #[test] fn ensure_quote_accuracy_with_jit_orders_fully_fills() { // Inaccuracy limit of 10% - let low_threshold = big_rational_from_decimal_str("0.1").unwrap(); - let high_threshold = big_rational_from_decimal_str("0.11").unwrap(); + let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.1").unwrap()); + let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.11").unwrap()); let sell_token: H160 = H160::from_low_u64_be(1); let buy_token: H160 = H160::from_low_u64_be(2); From a308910c8f6ec978a05576813fa2fe354c1bda42 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 1 Nov 2024 15:58:33 +0000 Subject: [PATCH 25/38] Avoid calculation with empty jit orders --- crates/shared/src/price_estimation/trade_verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 2d90b0a90d..44952c0528 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -669,7 +669,7 @@ fn ensure_quote_accuracy( ); let (expected_sell_token_lost, expected_buy_token_lost) = match trade { - TradeKind::Regular(trade) => { + TradeKind::Regular(trade) if !trade.jit_orders.is_empty() => { let jit_orders_net_token_changes = trade.jit_orders_net_token_changes()?; let jit_orders_sell_token_changes = jit_orders_net_token_changes .get(&query.sell_token) @@ -683,7 +683,7 @@ fn ensure_quote_accuracy( let expected_buy_token_lost = &buy_amount - jit_orders_buy_token_changes; (expected_sell_token_lost, expected_buy_token_lost) } - TradeKind::Legacy(_) => (BigRational::zero(), BigRational::zero()), + _ => (BigRational::zero(), BigRational::zero()), }; let sell_token_lost = &summary.sell_tokens_lost - expected_sell_token_lost; From 3067cd945a36eded563eea0a929f03007239eca2 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 5 Nov 2024 10:52:22 +0000 Subject: [PATCH 26/38] Address comments --- Cargo.lock | 1 + crates/e2e/Cargo.toml | 1 + crates/e2e/tests/e2e/quote_verification.rs | 4 +- .../src/price_estimation/trade_verifier.rs | 153 +++++++++--------- crates/shared/src/trade_finding.rs | 23 ++- 5 files changed, 94 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c82dd5395..1b54b0699b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,7 @@ dependencies = [ "async-trait", "autopilot", "axum", + "bigdecimal", "chrono", "clap", "contracts", diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index deaeacd4cd..a4c6476985 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -11,6 +11,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } autopilot = { path = "../autopilot" } axum = { workspace = true } +bigdecimal = { workspace = true } chrono = { workspace = true } clap = { workspace = true } contracts = { path = "../contracts" } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 4b1cfa51bc..5e442d2e35 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -1,4 +1,5 @@ use { + bigdecimal::{BigDecimal, Zero}, e2e::setup::*, ethcontract::H160, ethrpc::Web3, @@ -15,7 +16,6 @@ use { }, trade_finding::{Interaction, LegacyTrade, TradeKind}, }, - sqlx::types::BigDecimal, std::{str::FromStr, sync::Arc}, }; @@ -62,7 +62,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { block_stream, onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), - BigDecimal::from_str("0.0").unwrap(), + BigDecimal::zero(), ) .await .unwrap(); diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 44952c0528..e4acbba1da 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -5,7 +5,11 @@ use { code_simulation::CodeSimulating, encoded_settlement::{encode_trade, EncodedSettlement, EncodedTrade}, interaction::EncodedInteraction, - trade_finding::{external::dto, Interaction, TradeKind}, + trade_finding::{ + external::{dto, dto::JitOrder}, + Interaction, + TradeKind, + }, }, anyhow::{Context, Result}, bigdecimal::{BigDecimal, Zero}, @@ -407,15 +411,7 @@ fn encode_settlement( }; (tokens, prices) } - TradeKind::Regular(trade) => { - let mut tokens = Vec::with_capacity(trade.clearing_prices.len()); - let mut prices = Vec::with_capacity(trade.clearing_prices.len()); - for (token, price) in trade.clearing_prices.iter() { - tokens.push(*token); - prices.push(*price); - } - (tokens, prices) - } + TradeKind::Regular(trade) => trade.clearing_prices.iter().map(|e| e.to_owned()).unzip(), }; let fake_trade = encode_fake_trade(query, verification, out_amount, &tokens)?; @@ -428,8 +424,11 @@ fn encode_settlement( )?); } - let mut pre_interactions = verification.pre_interactions.clone(); - pre_interactions.extend(trade.pre_interactions().iter().cloned()); + let pre_interactions = [ + verification.pre_interactions.clone(), + trade.pre_interactions(), + ] + .concat(); Ok(EncodedSettlement { tokens, @@ -500,68 +499,75 @@ fn encode_jit_orders( tokens: &[H160], domain_separator: &DomainSeparator, ) -> Result, Error> { - let mut trades = Vec::with_capacity(jit_orders.len()); - - for jit_order in jit_orders.iter() { - let order_data = OrderData { - sell_token: jit_order.sell_token, - buy_token: jit_order.buy_token, - receiver: Some(jit_order.receiver), - sell_amount: jit_order.sell_amount, - buy_amount: jit_order.buy_amount, - valid_to: jit_order.valid_to, - app_data: jit_order.app_data, - fee_amount: 0.into(), - kind: match &jit_order.side { - dto::Side::Buy => OrderKind::Buy, - dto::Side::Sell => OrderKind::Sell, - }, - partially_fillable: jit_order.partially_fillable, - sell_token_balance: jit_order.sell_token_source, - buy_token_balance: jit_order.buy_token_destination, - }; - let (owner, signature) = match jit_order.signing_scheme { - SigningScheme::Eip1271 => { - let (owner, signature) = jit_order.signature.split_at(20); - let owner = H160::from_slice(owner); - let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; - (owner, signature) - } - SigningScheme::PreSign => { - let owner = H160::from_slice(&jit_order.signature); - let signature = - Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; - (owner, signature) - } - _ => { - let signature = - Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; - let owner = signature - .recover(domain_separator, &order_data.hash_struct())? - .context("could not recover the owner")? - .signer; - (owner, signature) - } - }; - - trades.push(encode_trade( - &order_data, - &signature, - owner, - // the tokens set length is small so the linear search is acceptable - tokens - .iter() - .position(|token| token == &jit_order.sell_token) - .context("missing jit order sell token index")?, - tokens - .iter() - .position(|token| token == &jit_order.buy_token) - .context("missing jit order buy token index")?, - &jit_order.executed_amount, - )); - } + jit_orders + .iter() + .map(|jit_order| { + let order_data = OrderData { + sell_token: jit_order.sell_token, + buy_token: jit_order.buy_token, + receiver: Some(jit_order.receiver), + sell_amount: jit_order.sell_amount, + buy_amount: jit_order.buy_amount, + valid_to: jit_order.valid_to, + app_data: jit_order.app_data, + fee_amount: 0.into(), + kind: match &jit_order.side { + dto::Side::Buy => OrderKind::Buy, + dto::Side::Sell => OrderKind::Sell, + }, + partially_fillable: jit_order.partially_fillable, + sell_token_balance: jit_order.sell_token_source, + buy_token_balance: jit_order.buy_token_destination, + }; + let (owner, signature) = recover_owner(jit_order, &order_data, domain_separator)?; + + Ok(encode_trade( + &order_data, + &signature, + owner, + // the tokens set length is small so the linear search is acceptable + tokens + .iter() + .position(|token| token == &jit_order.sell_token) + .context("missing jit order sell token index")?, + tokens + .iter() + .position(|token| token == &jit_order.buy_token) + .context("missing jit order buy token index")?, + &jit_order.executed_amount, + )) + }) + .collect::, Error>>() +} - Ok(trades) +/// Recovers the owner and signature from a `JitOrder`. +fn recover_owner( + jit_order: &JitOrder, + order_data: &OrderData, + domain_separator: &DomainSeparator, +) -> Result<(H160, Signature), Error> { + let (owner, signature) = match jit_order.signing_scheme { + SigningScheme::Eip1271 => { + let (owner, signature) = jit_order.signature.split_at(20); + let owner = H160::from_slice(owner); + let signature = Signature::from_bytes(jit_order.signing_scheme, signature)?; + (owner, signature) + } + SigningScheme::PreSign => { + let owner = H160::from_slice(&jit_order.signature); + let signature = Signature::from_bytes(jit_order.signing_scheme, Vec::new().as_slice())?; + (owner, signature) + } + _ => { + let signature = Signature::from_bytes(jit_order.signing_scheme, &jit_order.signature)?; + let owner = signature + .recover(domain_separator, &order_data.hash_struct())? + .context("could not recover the owner")? + .signer; + (owner, signature) + } + }; + Ok((owner, signature)) } /// Adds the interactions that are only needed to query important balances @@ -668,6 +674,7 @@ fn ensure_quote_accuracy( u256_to_big_rational(&buy_amount), ); + // Calculate the expected token losses that should be covered by JIT orders. let (expected_sell_token_lost, expected_buy_token_lost) = match trade { TradeKind::Regular(trade) if !trade.jit_orders.is_empty() => { let jit_orders_net_token_changes = trade.jit_orders_net_token_changes()?; diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 0c7f793800..d2ba26defd 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -16,7 +16,10 @@ use { num::{BigRational, CheckedDiv}, number::conversions::big_rational_to_u256, serde::Serialize, - std::{collections::HashMap, ops::Mul}, + std::{ + collections::HashMap, + ops::{AddAssign, Mul, SubAssign}, + }, thiserror::Error, }; @@ -195,10 +198,7 @@ impl Trade { .context("multiplication overflow in JIT order sell")? .checked_ceil_div(&buy_price) .context("division by zero in JIT order sell")?; - ( - executed_amount.to_big_rational(), - buy_amount.to_big_rational(), - ) + (executed_amount, buy_amount) } Side::Buy => { let sell_amount = executed_amount @@ -206,22 +206,19 @@ impl Trade { .context("multiplication overflow in JIT order buy")? .checked_ceil_div(&sell_price) .context("division by zero in JIT order buy")?; - ( - sell_amount.to_big_rational(), - executed_amount.to_big_rational(), - ) + (sell_amount, executed_amount) } }; net_token_changes .entry(jit_order.sell_token) - .and_modify(|e| *e += &executed_sell) - .or_insert(executed_sell); + .or_default() + .add_assign(executed_sell.to_big_rational()); net_token_changes .entry(jit_order.buy_token) - .and_modify(|e| *e -= &executed_buy) - .or_insert(-executed_buy); + .or_default() + .sub_assign(executed_buy.to_big_rational()); } Ok(net_token_changes) From 4a0767c2334da969042a1a50e2f3510d975ad633 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 5 Nov 2024 16:57:35 +0000 Subject: [PATCH 27/38] Naming --- crates/shared/src/price_estimation/trade_verifier.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index e4acbba1da..202fda8556 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -519,7 +519,8 @@ fn encode_jit_orders( sell_token_balance: jit_order.sell_token_source, buy_token_balance: jit_order.buy_token_destination, }; - let (owner, signature) = recover_owner(jit_order, &order_data, domain_separator)?; + let (owner, signature) = + recover_jit_order_owner(jit_order, &order_data, domain_separator)?; Ok(encode_trade( &order_data, @@ -541,7 +542,7 @@ fn encode_jit_orders( } /// Recovers the owner and signature from a `JitOrder`. -fn recover_owner( +fn recover_jit_order_owner( jit_order: &JitOrder, order_data: &OrderData, domain_separator: &DomainSeparator, From e051528ec920cea68563132780819506886aac5d Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 7 Nov 2024 10:04:13 +0000 Subject: [PATCH 28/38] Fetch the balances in the solver contract --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/solidity/Makefile | 2 +- crates/contracts/solidity/Solver.sol | 27 +- .../src/price_estimation/trade_verifier.rs | 314 +++++++----------- crates/shared/src/trade_finding.rs | 59 +--- 6 files changed, 142 insertions(+), 264 deletions(-) diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index 8248b515c2..a021bd0e68 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"buyToken","type":"address"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610992806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b5780639efde0c214610050575b600080fd5b61004e61004936600461074f565b61007a565b005b61006361005e366004610796565b6101af565b604051610071929190610894565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014291906108e2565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261092a565b6101939061116c610943565b6000808282546101a39190610943565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528b81166024830152604482018b905288811660648301528c16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c811660048301528e16602482015260006044820152309150633bbb2e1d90606401600060405180830381600087803b1580156103b957600080fd5b505af11580156103cd573d6000803e3d6000fd5b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b811660048301528f16602482015260006044820152309250633bbb2e1d9150606401600060405180830381600087803b15801561044757600080fd5b505af115801561045b573d6000803e3d6000fd5b5050505060005a90506104d186868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e73ffffffffffffffffffffffffffffffffffffffff1661067a90919063ffffffff16565b506000545a6104e0908361092a565b6104ea919061092a565b6040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528f166024820152600060448201529093503090633bbb2e1d90606401600060405180830381600087803b15801561056357600080fd5b505af1158015610577573d6000803e3d6000fd5b505050503073ffffffffffffffffffffffffffffffffffffffff16633bbb2e1d8a8f60006040518463ffffffff1660e01b81526004016105e59392919073ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152901515604082015260600190565b600060405180830381600087803b1580156105ff57600080fd5b505af1158015610613573d6000803e3d6000fd5b50505050600180548060200260200160405190810160405280929190818152602001828054801561066357602002820191906000526020600020905b81548152602001906001019080831161064f575b50505050509150509a509a98505050505050505050565b60606106888360008461068f565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516106b99190610956565b60006040518083038185875af1925050503d80600081146106f6576040519150601f19603f3d011682016040523d82523d6000602084013e6106fb565b606091505b50925090508061070d57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461073757600080fd5b50565b8035801515811461074a57600080fd5b919050565b60008060006060848603121561076457600080fd5b833561076f81610715565b9250602084013561077f81610715565b915061078d6040850161073a565b90509250925092565b6000806000806000806000806000806101208b8d0312156107b657600080fd5b8a356107c181610715565b995060208b01356107d181610715565b985060408b01356107e181610715565b975060608b0135965060808b01356107f881610715565b955060a08b013561080881610715565b945060c08b013561081881610715565b935060e08b013567ffffffffffffffff8082111561083557600080fd5b818d0191508d601f83011261084957600080fd5b81358181111561085857600080fd5b8e602082850101111561086a57600080fd5b6020830195508094505050506108836101008c0161073a565b90509295989b9194979a5092959850565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108d5578451835293830193918301916001016108b9565b5090979650505050505050565b6000602082840312156108f457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561093d5761093d6108fb565b92915050565b8082018082111561093d5761093d6108fb565b6000825160005b81811015610977576020818601810151858301520161095d565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b5780639efde0c214610050575b600080fd5b61004e61004936600461074f565b61007a565b005b61006361005e366004610796565b6101af565b604051610071929190610894565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014291906108e2565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261092a565b6101939061116c610943565b6000808282546101a39190610943565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528b81166024830152604482018b905288811660648301528c16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c811660048301528e16602482015260006044820152309150633bbb2e1d90606401600060405180830381600087803b1580156103b957600080fd5b505af11580156103cd573d6000803e3d6000fd5b50506040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b811660048301528f16602482015260006044820152309250633bbb2e1d9150606401600060405180830381600087803b15801561044757600080fd5b505af115801561045b573d6000803e3d6000fd5b5050505060005a90506104d186868080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e73ffffffffffffffffffffffffffffffffffffffff1661067a90919063ffffffff16565b506000545a6104e0908361092a565b6104ea919061092a565b6040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8d811660048301528f166024820152600060448201529093503090633bbb2e1d90606401600060405180830381600087803b15801561056357600080fd5b505af1158015610577573d6000803e3d6000fd5b505050503073ffffffffffffffffffffffffffffffffffffffff16633bbb2e1d8a8f60006040518463ffffffff1660e01b81526004016105e59392919073ffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152901515604082015260600190565b600060405180830381600087803b1580156105ff57600080fd5b505af1158015610613573d6000803e3d6000fd5b50505050600180548060200260200160405190810160405280929190818152602001828054801561066357602002820191906000526020600020905b81548152602001906001019080831161064f575b50505050509150509a509a98505050505050505050565b60606106888360008461068f565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516106b99190610956565b60006040518083038185875af1925050503d80600081146106f6576040519150601f19603f3d011682016040523d82523d6000602084013e6106fb565b606091505b50925090508061070d57815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461073757600080fd5b50565b8035801515811461074a57600080fd5b919050565b60008060006060848603121561076457600080fd5b833561076f81610715565b9250602084013561077f81610715565b915061078d6040850161073a565b90509250925092565b6000806000806000806000806000806101208b8d0312156107b657600080fd5b8a356107c181610715565b995060208b01356107d181610715565b985060408b01356107e181610715565b975060608b0135965060808b01356107f881610715565b955060a08b013561080881610715565b945060c08b013561081881610715565b935060e08b013567ffffffffffffffff8082111561083557600080fd5b818d0191508d601f83011261084957600080fd5b81358181111561085857600080fd5b8e602082850101111561086a57600080fd5b6020830195508094505050506108836101008c0161073a565b90509295989b9194979a5092959850565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156108d5578451835293830193918301916001016108b9565b5090979650505050505050565b6000602082840312156108f457600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561093d5761093d6108fb565b92915050565b8082018082111561093d5761093d6108fb565b6000825160005b81811015610977576020818601810151858301520161095d565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"uint256","name":"sellTokenIndex","type":"uint256"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x6080806040523461001657610a3f908161001c8239f35b600080fdfe60806040526004361015610013575b600080fd5b6000803560e01c9081633bbb2e1d1461003e575063c5746a0f1461003657600080fd5b61000e6102c8565b346101bc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bc57600435610079816101bf565b602435610085816101bf565b61008d6101ec565b9073ffffffffffffffffffffffffffffffffffffffff5a931673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81036100f657506100cc903161088b565b6100d4575080f35b6100ea6100e56100f1925a9061063b565b6108f4565b8254610903565b815580f35b608090817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806101636020957f70a082310000000000000000000000000000000000000000000000000000000084529073ffffffffffffffffffffffffffffffffffffffff60a49216608452565b01915afa80156101af575b839061017e575b6100cc9061088b565b5060203d81116101a8575b6101a1816101996100cc9361049f565b60800161085b565b9050610175565b503d610189565b6101b761054e565b61016e565b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b608435906101ea826101bf565b565b60443590811515820361000e57565b6101043590811515820361000e57565b602435906101ea826101bf565b60c435906101ea826101bf565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020838186019501011161000e57565b9060609160408101918152602092816040858094015285518094520193019160005b8281106102b4575050505090565b8351855293810193928101926001016102a6565b503461000e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57600435610305816101bf565b61030d61020b565b906103166101dd565b67ffffffffffffffff9260a43584811161000e57610338903690600401610225565b610340610218565b9160e43596871161000e5761035c610375973690600401610256565b9590946103676101fb565b9760643591604435916106be565b9061038560405192839283610284565b0390f35b1561039057565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e00000000000000006064820152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9190811015610455575b60051b0190565b61045d610414565b61044e565b3561046c816101bf565b90565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101166080016080811067ffffffffffffffff8211176104e157604052565b6104e961046f565b604052565b67ffffffffffffffff81116104e157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176104e157604052565b600091031261000e57565b506040513d6000823e3d90fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610597575b01160190565b61059f61046f565b610591565b3d156105cf573d906105b58261055b565b916105c36040519384610502565b82523d6000602084013e565b606090565b9291926105e08261055b565b916105ee6040519384610502565b82948184528183011161000e578281602093846000960137010152565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161064857565b6101ea61060b565b6040519081600180549081835260209081840192816000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6926000905b8282106106a6575050505050906101ea910383610502565b8454865288965094850194938301939083019061068e565b9798949a99908b9697949293916106d6303314610389565b610757575b5050505061074992916107396107419261073161074f996107288a8a6000998a8080808073ffffffffffffffffffffffffffffffffffffffff809c165af1506107226105a4565b50610910565b5a9436916105d4565b908816610a09565b505a9061063b565b90549061063b565b94610910565b61046c610650565b73ffffffffffffffffffffffffffffffffffffffff169895509093916107879061078290888d610444565b610462565b92883b1561000e57610731610749968c9660006107399561074f9d61074199838f610816604051978896879586947f57d5a1d3000000000000000000000000000000000000000000000000000000008652600486019293606092919594608085019673ffffffffffffffffffffffffffffffffffffffff93848092168752166020860152604085015216910152565b03925af1801561084e575b610835575b509950839596508194506106db565b80610842610848926104ee565b80610543565b38610826565b61085661054e565b610821565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80602091011261000e5760805190565b600154680100000000000000008110156108e7575b60018101806001558110156108da575b60016000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6108e2610414565b6108b0565b6108ef61046f565b6108a0565b9061116c820180921161064857565b9190820180921161064857565b92919060005b818110610924575050509050565b61092f818387610444565b359061093a826101bf565b303b1561000e576040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529184166024830152600060448301819052600192908160648183305af180156109fc575b6109e9575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109dc575b01610916565b6109e461060b565b6109d6565b806108426109f6926104ee565b386109ad565b610a0461054e565b6109a8565b600091829182602083519301915af190610a216105a4565b9115610a2957565b50602081519101fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361015610013575b600080fd5b6000803560e01c9081633bbb2e1d1461003e575063c5746a0f1461003657600080fd5b61000e6102c8565b346101bc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bc57600435610079816101bf565b602435610085816101bf565b61008d6101ec565b9073ffffffffffffffffffffffffffffffffffffffff5a931673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81036100f657506100cc903161088b565b6100d4575080f35b6100ea6100e56100f1925a9061063b565b6108f4565b8254610903565b815580f35b608090817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806101636020957f70a082310000000000000000000000000000000000000000000000000000000084529073ffffffffffffffffffffffffffffffffffffffff60a49216608452565b01915afa80156101af575b839061017e575b6100cc9061088b565b5060203d81116101a8575b6101a1816101996100cc9361049f565b60800161085b565b9050610175565b503d610189565b6101b761054e565b61016e565b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b608435906101ea826101bf565b565b60443590811515820361000e57565b6101043590811515820361000e57565b602435906101ea826101bf565b60c435906101ea826101bf565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020838186019501011161000e57565b9060609160408101918152602092816040858094015285518094520193019160005b8281106102b4575050505090565b8351855293810193928101926001016102a6565b503461000e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57600435610305816101bf565b61030d61020b565b906103166101dd565b67ffffffffffffffff9260a43584811161000e57610338903690600401610225565b610340610218565b9160e43596871161000e5761035c610375973690600401610256565b9590946103676101fb565b9760643591604435916106be565b9061038560405192839283610284565b0390f35b1561039057565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e00000000000000006064820152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9190811015610455575b60051b0190565b61045d610414565b61044e565b3561046c816101bf565b90565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101166080016080811067ffffffffffffffff8211176104e157604052565b6104e961046f565b604052565b67ffffffffffffffff81116104e157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176104e157604052565b600091031261000e57565b506040513d6000823e3d90fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610597575b01160190565b61059f61046f565b610591565b3d156105cf573d906105b58261055b565b916105c36040519384610502565b82523d6000602084013e565b606090565b9291926105e08261055b565b916105ee6040519384610502565b82948184528183011161000e578281602093846000960137010152565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161064857565b6101ea61060b565b6040519081600180549081835260209081840192816000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6926000905b8282106106a6575050505050906101ea910383610502565b8454865288965094850194938301939083019061068e565b9798949a99908b9697949293916106d6303314610389565b610757575b5050505061074992916107396107419261073161074f996107288a8a6000998a8080808073ffffffffffffffffffffffffffffffffffffffff809c165af1506107226105a4565b50610910565b5a9436916105d4565b908816610a09565b505a9061063b565b90549061063b565b94610910565b61046c610650565b73ffffffffffffffffffffffffffffffffffffffff169895509093916107879061078290888d610444565b610462565b92883b1561000e57610731610749968c9660006107399561074f9d61074199838f610816604051978896879586947f57d5a1d3000000000000000000000000000000000000000000000000000000008652600486019293606092919594608085019673ffffffffffffffffffffffffffffffffffffffff93848092168752166020860152604085015216910152565b03925af1801561084e575b610835575b509950839596508194506106db565b80610842610848926104ee565b80610543565b38610826565b61085661054e565b610821565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80602091011261000e5760805190565b600154680100000000000000008110156108e7575b60018101806001558110156108da575b60016000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6108e2610414565b6108b0565b6108ef61046f565b6108a0565b9061116c820180921161064857565b9190820180921161064857565b92919060005b818110610924575050509050565b61092f818387610444565b359061093a826101bf565b303b1561000e576040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529184166024830152600060448301819052600192908160648183305af180156109fc575b6109e9575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109dc575b01610916565b6109e461060b565b6109d6565b806108426109f6926104ee565b386109ad565b610a0461054e565b6109a8565b600091829182602083519301915af190610a216105a4565b9115610a2957565b50602081519101fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index f2a600e845..c6d4e54bf5 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610bb5806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x6080806040523461001657610b21908161001c8239f35b600080fdfe6080604052600436101561001e575b361561001c5761001c610667565b005b6000803560e01c9081631626ba7e1461004957506357d5a1d30361000e57610044610123565b61000e565b346100f95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100f95760243567ffffffffffffffff8082116100fc57366023830112156100fc5781600401359081116100fc57369101602401116100f9577f1626ba7e000000000000000000000000000000000000000000000000000000006080527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8060a0016080f35b80fd5b8280fd5b73ffffffffffffffffffffffffffffffffffffffff81160361011e57565b600080fd5b503461011e5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011e57600480359061016182610100565b6024359061016e82610100565b604435916064359361017f85610100565b6101bb6101b66101b27f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c973490600182549255565b1590565b6106bb565b73ffffffffffffffffffffffffffffffffffffffff8080961692169180831461044c575b5083926102e79261001c9661028c9316604051907f9b552cc200000000000000000000000000000000000000000000000000000000918281526020978896878381818a8297895afa90811561043f575b600091610422575b50604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152308b820190815273ffffffffffffffffffffffffffffffffffffffff909316602084015293849283920190565b0381895afa908115610415575b6000916103f8575b501061033e575b50506040517f70a08231000000000000000000000000000000000000000000000000000000008152309381019384529485938492508291602090910190565b03915afa918215610331575b600092610304575b50501015610826565b6103239250803d1061032a575b61031b81836105dd565b810190610746565b38806102fb565b503d610311565b610339610755565b6102f3565b61038c9261036960405182815283818981885afa9081156103eb575b6000916103ce575b5086610939565b856040518094819382525afa9081156103c1575b600091610394575b50826109c3565b3880846102a8565b6103b49150853d87116103ba575b6103ac81836105dd565b81019061080e565b38610385565b503d6103a2565b6103c9610755565b61037d565b6103e59150843d86116103ba576103ac81836105dd565b38610362565b6103f3610755565b61035a565b61040f9150833d851161032a5761031b81836105dd565b386102a1565b61041d610755565b610299565b6104399150823d84116103ba576103ac81836105dd565b38610237565b610447610755565b61022f565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152308582019081526020908290819083010381875afa908115610563575b600091610545575b508581106104a6575b506101df565b6104b4908695929395610762565b926104c18447101561079e565b823b1561011e5761001c9686956102e795600061028c9686604051809481937fd0e30db00000000000000000000000000000000000000000000000000000000083525af18015610538575b61051f575b5093509650928194506104a0565b8061052c610532926105a0565b80610803565b38610511565b610540610755565b61050c565b61055d915060203d811161032a5761031b81836105dd565b38610497565b61056b610755565b61048f565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81116105b457604052565b6105bc610570565b604052565b6080810190811067ffffffffffffffff8211176105b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105b457604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff811161065a575b01160190565b610662610570565b610654565b506000806106743661061e565b61068160405191826105dd565b36815260208101903683833782602036830101525190620100005af46106a56108b1565b90156106b357602081519101f35b602081519101fd5b156106c257565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e636500000000000000000000000000000000000000000000000000000000006064820152fd5b9081602091031261011e575190565b506040513d6000823e3d90fd5b9190820391821161076f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b156107a557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152fd5b600091031261011e57565b9081602091031261011e575161082381610100565b90565b1561082d57565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152fd5b3d156108dc573d906108c28261061e565b916108d060405193846105dd565b82523d6000602084013e565b606090565b604051906040820182811067ffffffffffffffff82111761092c575b604052601a82527f5361666545524332303a20617070726f76616c206661696c65640000000000006020830152565b610934610570565b6108fd565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201528260448201526044815261099c816105c1565b5193165af16109a96108b1565b90156106b3576109c1906109bb6108e1565b90610a46565b565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60448201526044815261099c816105c1565b8051908115918215610af1575b505015610a5d5750565b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110610ada575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201610a99565b819250906020918101031261011e5760200151801515810361011e573880610a5356fea164736f6c6343000811000a","deployedBytecode":"0x6080604052600436101561001e575b361561001c5761001c610667565b005b6000803560e01c9081631626ba7e1461004957506357d5a1d30361000e57610044610123565b61000e565b346100f95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100f95760243567ffffffffffffffff8082116100fc57366023830112156100fc5781600401359081116100fc57369101602401116100f9577f1626ba7e000000000000000000000000000000000000000000000000000000006080527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8060a0016080f35b80fd5b8280fd5b73ffffffffffffffffffffffffffffffffffffffff81160361011e57565b600080fd5b503461011e5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011e57600480359061016182610100565b6024359061016e82610100565b604435916064359361017f85610100565b6101bb6101b66101b27f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c973490600182549255565b1590565b6106bb565b73ffffffffffffffffffffffffffffffffffffffff8080961692169180831461044c575b5083926102e79261001c9661028c9316604051907f9b552cc200000000000000000000000000000000000000000000000000000000918281526020978896878381818a8297895afa90811561043f575b600091610422575b50604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152308b820190815273ffffffffffffffffffffffffffffffffffffffff909316602084015293849283920190565b0381895afa908115610415575b6000916103f8575b501061033e575b50506040517f70a08231000000000000000000000000000000000000000000000000000000008152309381019384529485938492508291602090910190565b03915afa918215610331575b600092610304575b50501015610826565b6103239250803d1061032a575b61031b81836105dd565b810190610746565b38806102fb565b503d610311565b610339610755565b6102f3565b61038c9261036960405182815283818981885afa9081156103eb575b6000916103ce575b5086610939565b856040518094819382525afa9081156103c1575b600091610394575b50826109c3565b3880846102a8565b6103b49150853d87116103ba575b6103ac81836105dd565b81019061080e565b38610385565b503d6103a2565b6103c9610755565b61037d565b6103e59150843d86116103ba576103ac81836105dd565b38610362565b6103f3610755565b61035a565b61040f9150833d851161032a5761031b81836105dd565b386102a1565b61041d610755565b610299565b6104399150823d84116103ba576103ac81836105dd565b38610237565b610447610755565b61022f565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152308582019081526020908290819083010381875afa908115610563575b600091610545575b508581106104a6575b506101df565b6104b4908695929395610762565b926104c18447101561079e565b823b1561011e5761001c9686956102e795600061028c9686604051809481937fd0e30db00000000000000000000000000000000000000000000000000000000083525af18015610538575b61051f575b5093509650928194506104a0565b8061052c610532926105a0565b80610803565b38610511565b610540610755565b61050c565b61055d915060203d811161032a5761031b81836105dd565b38610497565b61056b610755565b61048f565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81116105b457604052565b6105bc610570565b604052565b6080810190811067ffffffffffffffff8211176105b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105b457604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff811161065a575b01160190565b610662610570565b610654565b506000806106743661061e565b61068160405191826105dd565b36815260208101903683833782602036830101525190620100005af46106a56108b1565b90156106b357602081519101f35b602081519101fd5b156106c257565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e636500000000000000000000000000000000000000000000000000000000006064820152fd5b9081602091031261011e575190565b506040513d6000823e3d90fd5b9190820391821161076f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b156107a557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152fd5b600091031261011e57565b9081602091031261011e575161082381610100565b90565b1561082d57565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152fd5b3d156108dc573d906108c28261061e565b916108d060405193846105dd565b82523d6000602084013e565b606090565b604051906040820182811067ffffffffffffffff82111761092c575b604052601a82527f5361666545524332303a20617070726f76616c206661696c65640000000000006020830152565b610934610570565b6108fd565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201528260448201526044815261099c816105c1565b5193165af16109a96108b1565b90156106b3576109c1906109bb6108e1565b90610a46565b565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60448201526044815261099c816105c1565b8051908115918215610af1575b505015610a5d5750565b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110610ada575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201610a99565b819250906020918101031261011e5760200151801515810361011e573880610a5356fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Makefile b/crates/contracts/solidity/Makefile index 5816391d8d..b12596d025 100644 --- a/crates/contracts/solidity/Makefile +++ b/crates/contracts/solidity/Makefile @@ -2,7 +2,7 @@ DOCKER := docker JQ := jq SOLC := ethereum/solc:0.8.17 -SOLFLAGS := --overwrite --abi --bin --bin-runtime --metadata-hash none --optimize --optimize-runs 1000000 +SOLFLAGS := --overwrite --abi --bin --bin-runtime --metadata-hash none --optimize --optimize-runs 1000000 --via-ir TARGETDIR := ../../../target/solidity ARTIFACTDIR := ../artifacts diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index 8e9236b20a..a912918f9f 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -29,10 +29,10 @@ contract Solver { /// @param settlementContract - address of the settlement contract because /// it does not have a stable address in tests. /// @param trader - address of the order owner doing the trade - /// @param sellToken - address of the token being sold + /// @param sellTokenIndex - index in the tokens array of the token being sold /// @param sellAmount - amount being sold - /// @param buyToken - address of the token being bought /// @param nativeToken - ERC20 version of the chain's token + /// @param tokens - list of tokens used in the trade /// @param receiver - address receiving the bought tokens /// @param settlementCall - the calldata of the `settle()` call /// @param mockPreconditions - controls whether things like ETH wrapping @@ -45,10 +45,10 @@ contract Solver { function swap( ISettlement settlementContract, address payable trader, - address sellToken, + uint256 sellTokenIndex, uint256 sellAmount, - address buyToken, address nativeToken, + address[] calldata tokens, address payable receiver, bytes calldata settlementCall, bool mockPreconditions @@ -64,7 +64,7 @@ contract Solver { Trader(trader) .prepareSwap( settlementContract, - sellToken, + tokens[sellTokenIndex], sellAmount, nativeToken ); @@ -76,13 +76,16 @@ contract Solver { // contract. receiver.call{value: 0}(""); - this.storeBalance(sellToken, address(settlementContract), false); - this.storeBalance(buyToken, address(settlementContract), false); + // Store pre-settlement balances + _storeSettlementBalances(tokens, settlementContract); + uint256 gasStart = gasleft(); address(settlementContract).doCall(settlementCall); gasUsed = gasStart - gasleft() - _simulationOverhead; - this.storeBalance(sellToken, address(settlementContract), false); - this.storeBalance(buyToken, address(settlementContract), false); + + // Store post-settlement balances + _storeSettlementBalances(tokens, settlementContract); + queriedBalances = _queriedBalances; } @@ -104,4 +107,10 @@ contract Solver { _simulationOverhead += gasStart - gasleft() + 4460; } } + + function _storeSettlementBalances(address[] calldata tokens, ISettlement settlementContract) internal { + for (uint256 i = 0; i < tokens.length; i++) { + this.storeBalance(tokens[i], address(settlementContract), false); + } + } } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 202fda8556..652e418aca 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -12,7 +12,7 @@ use { }, }, anyhow::{Context, Result}, - bigdecimal::{BigDecimal, Zero}, + bigdecimal::BigDecimal, contracts::{ deployed_bytecode, dummy_contract, @@ -110,10 +110,24 @@ impl TradeVerifier { let solver = trade.tx_origin().unwrap_or(trade.solver()); let solver = dummy_contract!(Solver, solver); + let (tokens, clearing_prices) = match trade { + TradeKind::Legacy(_) => { + let tokens = vec![query.sell_token, query.buy_token]; + let prices = match query.kind { + OrderKind::Sell => vec![*out_amount, query.in_amount.get()], + OrderKind::Buy => vec![query.in_amount.get(), *out_amount], + }; + (tokens, prices) + } + TradeKind::Regular(trade) => trade.clearing_prices.iter().map(|e| e.to_owned()).unzip(), + }; + let settlement = encode_settlement( query, verification, trade, + &tokens, + &clearing_prices, out_amount, self.native_token, &self.domain_separator, @@ -148,10 +162,14 @@ impl TradeVerifier { .swap( self.settlement.address(), verification.from, - query.sell_token, + tokens + .iter() + .position(|&t| t == query.sell_token) + .context("missing query sell token")? + .into(), sell_amount, - query.buy_token, self.native_token, + tokens.clone(), verification.receiver, Bytes(settlement.data.unwrap().0), // only if the user did not provide pre-interactions is it safe @@ -212,7 +230,7 @@ impl TradeVerifier { } }; - let mut summary = SettleOutput::decode(&output?, query.kind) + let mut summary = SettleOutput::decode(&output?, query.kind, &tokens) .context("could not decode simulation output") .map_err(Error::SimulationFailed)?; @@ -228,19 +246,24 @@ impl TradeVerifier { // It looks like the contract lost a lot of sell tokens but only because it was // the trader and had to pay for the trade. Adjust tokens lost downward. if verification.from == self.settlement.address() { - summary.sell_tokens_lost -= u256_to_big_rational(&sell_amount); + summary + .tokens_lost + .entry(query.sell_token) + .and_modify(|balance| *balance -= u256_to_big_rational(&sell_amount)); } // It looks like the contract gained a lot of buy tokens (negative loss) but // only because it was the receiver and got the payout. Adjust the tokens lost // upward. if verification.receiver == self.settlement.address() { - summary.buy_tokens_lost += u256_to_big_rational(&buy_amount); + summary + .tokens_lost + .entry(query.buy_token) + .and_modify(|balance| *balance += u256_to_big_rational(&buy_amount)); } } tracing::debug!( - lost_buy_amount = %summary.buy_tokens_lost, - lost_sell_amount = %summary.sell_tokens_lost, + tokens_lost = ?summary.tokens_lost, gas_diff = ?trade.gas_estimate().unwrap_or_default().abs_diff(summary.gas_used.as_u64()), time = ?start.elapsed(), promised_out_amount = ?out_amount, @@ -378,10 +401,13 @@ fn encode_interactions(interactions: &[Interaction]) -> Vec interactions.iter().map(|i| i.encode()).collect() } +#[allow(clippy::too_many_arguments)] fn encode_settlement( query: &PriceQuery, verification: &Verification, trade: &TradeKind, + tokens: &[H160], + clearing_prices: &[U256], out_amount: &U256, native_token: H160, domain_separator: &DomainSeparator, @@ -402,24 +428,12 @@ fn encode_settlement( tracing::trace!("adding unwrap interaction for paying out ETH"); } - let (tokens, clearing_prices) = match trade { - TradeKind::Legacy(_) => { - let tokens = vec![query.sell_token, query.buy_token]; - let prices = match query.kind { - OrderKind::Sell => vec![*out_amount, query.in_amount.get()], - OrderKind::Buy => vec![query.in_amount.get(), *out_amount], - }; - (tokens, prices) - } - TradeKind::Regular(trade) => trade.clearing_prices.iter().map(|e| e.to_owned()).unzip(), - }; - - let fake_trade = encode_fake_trade(query, verification, out_amount, &tokens)?; + let fake_trade = encode_fake_trade(query, verification, out_amount, tokens)?; let mut trades = vec![fake_trade]; if let TradeKind::Regular(trade) = trade { trades.extend(encode_jit_orders( &trade.jit_orders, - &tokens, + tokens, domain_separator, )?); } @@ -431,8 +445,8 @@ fn encode_settlement( .concat(); Ok(EncodedSettlement { - tokens, - clearing_prices, + tokens: tokens.to_vec(), + clearing_prices: clearing_prices.to_vec(), trades, interactions: [ encode_interactions(&pre_interactions), @@ -598,8 +612,8 @@ fn add_balance_queries( let query_balance = solver.methods().store_balance(token, owner, true); let query_balance = Bytes(query_balance.tx.data.unwrap().0); let interaction = (solver.address(), 0.into(), query_balance); - // query balance right after we receive all `sell_token` - settlement.interactions[1].insert(0, interaction.clone()); + // query balance query at the end of pre-interactions + settlement.interactions[0].push(interaction.clone()); // query balance right after we payed out all `buy_token` settlement.interactions[2].insert(0, interaction); settlement @@ -613,16 +627,12 @@ struct SettleOutput { /// `out_amount` perceived by the trader (sell token for buy orders or buy /// token for sell order) out_amount: U256, - /// Difference in buy tokens of the settlement contract before and after the - /// trade. - buy_tokens_lost: BigRational, - /// Difference in sell tokens of the settlement contract before and after - /// the trade. - sell_tokens_lost: BigRational, + /// Tokens difference of the settlement contract before and after the trade. + tokens_lost: HashMap, } impl SettleOutput { - fn decode(output: &[u8], kind: OrderKind) -> Result { + fn decode(output: &[u8], kind: OrderKind, tokens_vec: &[H160]) -> Result { let function = Solver::raw_contract() .interface .abi @@ -631,14 +641,27 @@ impl SettleOutput { let tokens = function.decode_output(output).context("decode")?; let (gas_used, balances): (U256, Vec) = Tokenize::from_token(Token::Tuple(tokens))?; - let settlement_sell_balance_before = u256_to_big_rational(&balances[0]); - let settlement_buy_balance_before = u256_to_big_rational(&balances[1]); - - let trader_balance_before = balances[2]; - let trader_balance_after = balances[3]; + let mut i = 0; + let mut tokens_lost = HashMap::new(); + // Get settlement contract balances before the trade + for token in tokens_vec.iter() { + let balance_before = u256_to_big_rational(&balances[i]); + tokens_lost.insert(*token, balance_before); + i += 1; + } - let settlement_sell_balance_after = u256_to_big_rational(&balances[4]); - let settlement_buy_balance_after = u256_to_big_rational(&balances[5]); + let trader_balance_before = balances[i]; + let trader_balance_after = balances[i + 1]; + i += 2; + + // Get settlement contract balances after the trade + for token in tokens_vec.iter() { + let balance_after = u256_to_big_rational(&balances[i]); + tokens_lost + .entry(*token) + .and_modify(|balance_before| *balance_before -= balance_after); + i += 1; + } let out_amount = match kind { // for sell orders we track the buy_token amount which increases during the settlement @@ -651,8 +674,7 @@ impl SettleOutput { Ok(SettleOutput { gas_used, out_amount, - buy_tokens_lost: settlement_buy_balance_before - settlement_buy_balance_after, - sell_tokens_lost: settlement_sell_balance_before - settlement_sell_balance_after, + tokens_lost, }) } } @@ -674,32 +696,19 @@ fn ensure_quote_accuracy( u256_to_big_rational(&sell_amount), u256_to_big_rational(&buy_amount), ); - - // Calculate the expected token losses that should be covered by JIT orders. - let (expected_sell_token_lost, expected_buy_token_lost) = match trade { - TradeKind::Regular(trade) if !trade.jit_orders.is_empty() => { - let jit_orders_net_token_changes = trade.jit_orders_net_token_changes()?; - let jit_orders_sell_token_changes = jit_orders_net_token_changes - .get(&query.sell_token) - .context("missing jit orders sell token executed amount")?; - let jit_orders_buy_token_changes = - jit_orders_net_token_changes - .get(&query.buy_token) - .context("missing jit orders buy token executed amount")?; - - let expected_sell_token_lost = -&sell_amount - jit_orders_sell_token_changes; - let expected_buy_token_lost = &buy_amount - jit_orders_buy_token_changes; - (expected_sell_token_lost, expected_buy_token_lost) - } - _ => (BigRational::zero(), BigRational::zero()), - }; - - let sell_token_lost = &summary.sell_tokens_lost - expected_sell_token_lost; let sell_token_lost_limit = inaccuracy_limit * &sell_amount; - let buy_token_lost = &summary.buy_tokens_lost - expected_buy_token_lost; let buy_token_lost_limit = inaccuracy_limit * &buy_amount; - if sell_token_lost >= sell_token_lost_limit || buy_token_lost >= buy_token_lost_limit { + let sell_token_lost = summary + .tokens_lost + .get(&query.sell_token) + .context("summary sell token is missing")?; + let buy_token_lost = summary + .tokens_lost + .get(&query.buy_token) + .context("summary buy token is missing")?; + + if *sell_token_lost >= sell_token_lost_limit || *buy_token_lost >= buy_token_lost_limit { return Err(Error::TooInaccurate); } @@ -737,7 +746,6 @@ mod tests { super::*, crate::trade_finding::Trade, app_data::AppDataHash, - bigdecimal::FromPrimitive, model::order::{BuyTokenDestination, SellTokenSource}, std::str::FromStr, }; @@ -749,18 +757,25 @@ mod tests { let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.5").unwrap()); let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.51").unwrap()); + let sell_token = H160([1u8; 20]); + let buy_token = H160([2u8; 20]); + let query = PriceQuery { in_amount: 1_000.try_into().unwrap(), kind: OrderKind::Sell, - sell_token: H160::zero(), - buy_token: H160::zero(), + sell_token, + buy_token, + }; + + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(500.into()), + buy_token => BigRational::from_integer(0.into()), }; let sell_more = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(0.into()), - sell_tokens_lost: BigRational::from_integer(500.into()), + tokens_lost, }; let trade = TradeKind::Legacy(Default::default()); @@ -771,11 +786,15 @@ mod tests { let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &sell_more); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(0.into()), + buy_token => BigRational::from_integer(1_000.into()), + }; + let pay_out_more = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(1_000.into()), - sell_tokens_lost: BigRational::from_integer(0.into()), + tokens_lost, }; let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_more); @@ -785,21 +804,29 @@ mod tests { let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &pay_out_more); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer((-500).into()), + buy_token => BigRational::from_integer(0.into()), + }; + let sell_less = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer(0.into()), - sell_tokens_lost: BigRational::from_integer((-500).into()), + tokens_lost, }; // Ending up with surplus in the buffers is always fine let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &sell_less); assert!(estimate.is_ok()); + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(0.into()), + buy_token => BigRational::from_integer((-1_000).into()), + }; + let pay_out_less = SettleOutput { gas_used: 0.into(), out_amount: 2_000.into(), - buy_tokens_lost: BigRational::from_integer((-1_000).into()), - sell_tokens_lost: BigRational::from_integer(0.into()), + tokens_lost, }; // Ending up with surplus in the buffers is always fine let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_less); @@ -807,7 +834,7 @@ mod tests { } #[test] - fn ensure_quote_accuracy_with_jit_orders_partial_fills() { + fn ensure_quote_accuracy_with_jit_orders() { // Inaccuracy limit of 10% let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.1").unwrap()); let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.11").unwrap()); @@ -815,7 +842,6 @@ mod tests { let sell_token: H160 = H160::from_low_u64_be(1); let buy_token: H160 = H160::from_low_u64_be(2); - // Clearing prices: Token A = 1, Token B = 2 let mut clearing_prices = HashMap::new(); clearing_prices.insert(sell_token, U256::from(1u64)); clearing_prices.insert(buy_token, U256::from(2u64)); @@ -836,117 +862,10 @@ mod tests { ]; let jit_orders = [ + // Sell order dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(200u64), // Fills the query partially - side: dto::Side::Sell, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }, - dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A - executed_amount: U256::from(400u64), // Fills the query partially - side: dto::Side::Buy, - sell_amount: U256::from(200u64), - buy_amount: U256::from(400u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }, - ]; - - for (query, jit_order) in queries - .iter() - .flat_map(|query| jit_orders.iter().map(move |jit_order| (query, jit_order))) - { - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order.clone()], - }); - - // The out amount is the total amount of buy token the user gets - let out_amount = match (&query.kind, &jit_order.side) { - (OrderKind::Sell, dto::Side::Sell) => U256::from(250u64), - (OrderKind::Buy, dto::Side::Buy) => U256::from(500u64), - (OrderKind::Sell, dto::Side::Buy) => U256::from(250u64), - (OrderKind::Buy, dto::Side::Sell) => U256::from(500u64), - }; - - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - out_amount, - // Settlement contract covers the remaining 50 units of Token B, but to test the 11% - // limit we lose a bit more. - buy_tokens_lost: BigRational::from_u64(75).unwrap(), - // Settlement contract gains 100 units of Token A. To test the 11% limit we gain a - // bit more. - sell_tokens_lost: BigRational::from_i64(-150).unwrap(), - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - } - - #[test] - fn ensure_quote_accuracy_with_jit_orders_fully_fills() { - // Inaccuracy limit of 10% - let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.1").unwrap()); - let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.11").unwrap()); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - // Clearing prices: Token A = 1, Token B = 2 - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); - - let queries = [ - PriceQuery { - in_amount: NonZeroU256::new(500u64.into()).unwrap(), - kind: OrderKind::Sell, - sell_token, - buy_token, - }, - PriceQuery { - in_amount: NonZeroU256::new(250u64.into()).unwrap(), - kind: OrderKind::Buy, - sell_token, - buy_token, - }, - ]; - - let jit_orders = [ - dto::JitOrder { - sell_token: buy_token, // Solver sells Token B - buy_token: sell_token, // Solver buys Token A + sell_token: buy_token, // Solver sells buy token + buy_token: sell_token, // Solver buys sell token executed_amount: U256::from(250u64), // Fully fills the query side: dto::Side::Sell, sell_amount: U256::from(250u64), @@ -960,9 +879,10 @@ mod tests { signature: vec![], signing_scheme: SigningScheme::Eip1271, }, + // Buy order dto::JitOrder { - sell_token: buy_token, // Solver sell Token B - buy_token: sell_token, // Solver buys Token A + sell_token: buy_token, // Solver sells buy token + buy_token: sell_token, // Solver buys sell token executed_amount: U256::from(500u64), // Fully fills the query side: dto::Side::Buy, sell_amount: U256::from(250u64), @@ -1000,15 +920,17 @@ mod tests { (OrderKind::Buy, dto::Side::Sell) => U256::from(500u64), }; + // The Settlement contract is not expected to lose any tokens, but we lose a bit + // to test the inaccuracy limit. + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(50.into()), + buy_token => BigRational::from_integer(25.into()), + }; + let summary = SettleOutput { gas_used: U256::from(50_000u64), out_amount, - // Settlement is not expected to lose any buy tokens, but we lose a bit more to test - // the inaccuracy limit. - buy_tokens_lost: BigRational::from_u64(25).unwrap(), - // Settlement is not expected to gain any sell tokens, but we gain a bit more to - // test the inaccuracy limit. - sell_tokens_lost: BigRational::from_i64(-50).unwrap(), + tokens_lost, }; // The summary has 10% inaccuracy diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index d2ba26defd..3aad80630a 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -7,19 +7,16 @@ use { crate::{ conversions::U256Ext, price_estimation::{PriceEstimationError, Query}, - trade_finding::external::{dto, dto::Side}, + trade_finding::external::dto, }, anyhow::{Context, Result}, derivative::Derivative, ethcontract::{contract::MethodBuilder, tokens::Tokenize, web3::Transport, Bytes, H160, U256}, model::{interaction::InteractionData, order::OrderKind}, - num::{BigRational, CheckedDiv}, + num::CheckedDiv, number::conversions::big_rational_to_u256, serde::Serialize, - std::{ - collections::HashMap, - ops::{AddAssign, Mul, SubAssign}, - }, + std::{collections::HashMap, ops::Mul}, thiserror::Error, }; @@ -173,56 +170,6 @@ impl Trade { big_rational_to_u256(&out_amount).context("out amount is not a valid U256") } - - /// Calculate the net token changes for all the JIT orders. - /// Positive values mean the solver gains the token, negative values mean - /// the solver loses it. - pub fn jit_orders_net_token_changes(&self) -> Result> { - let mut net_token_changes: HashMap = HashMap::new(); - - for jit_order in self.jit_orders.iter() { - let sell_price = *self - .clearing_prices - .get(&jit_order.sell_token) - .context("JIT order sell token clearing price is missing")?; - let buy_price = *self - .clearing_prices - .get(&jit_order.buy_token) - .context("JIT order buy token clearing price is missing")?; - let executed_amount = jit_order.executed_amount; - - let (executed_sell, executed_buy) = match jit_order.side { - Side::Sell => { - let buy_amount = executed_amount - .checked_mul(sell_price) - .context("multiplication overflow in JIT order sell")? - .checked_ceil_div(&buy_price) - .context("division by zero in JIT order sell")?; - (executed_amount, buy_amount) - } - Side::Buy => { - let sell_amount = executed_amount - .checked_mul(buy_price) - .context("multiplication overflow in JIT order buy")? - .checked_ceil_div(&sell_price) - .context("division by zero in JIT order buy")?; - (sell_amount, executed_amount) - } - }; - - net_token_changes - .entry(jit_order.sell_token) - .or_default() - .add_assign(executed_sell.to_big_rational()); - - net_token_changes - .entry(jit_order.buy_token) - .or_default() - .sub_assign(executed_buy.to_big_rational()); - } - - Ok(net_token_changes) - } } /// Data for a raw GPv2 interaction. From 2bcb3ac3f1363e732aa4de6ee18a9d86e1e79f50 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 7 Nov 2024 10:57:58 +0000 Subject: [PATCH 29/38] Refactor the unit test --- .../src/price_estimation/trade_verifier.rs | 102 +++++++++--------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 652e418aca..b00b9dfc3e 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -846,47 +846,37 @@ mod tests { clearing_prices.insert(sell_token, U256::from(1u64)); clearing_prices.insert(buy_token, U256::from(2u64)); - let queries = [ - PriceQuery { - in_amount: NonZeroU256::new(500u64.into()).unwrap(), - kind: OrderKind::Sell, - sell_token, - buy_token, - }, - PriceQuery { - in_amount: NonZeroU256::new(250u64.into()).unwrap(), - kind: OrderKind::Buy, + let query_with_out_amount = |kind: OrderKind| -> (PriceQuery, U256) { + let (in_amount, out_amount) = match kind { + OrderKind::Sell => (500u64.into(), 250u64.into()), + OrderKind::Buy => (250u64.into(), 500u64.into()), + }; + let price_query = PriceQuery { + in_amount: NonZeroU256::new(in_amount).unwrap(), + kind, sell_token, buy_token, - }, - ]; + }; - let jit_orders = [ - // Sell order - dto::JitOrder { - sell_token: buy_token, // Solver sells buy token - buy_token: sell_token, // Solver buys sell token - executed_amount: U256::from(250u64), // Fully fills the query - side: dto::Side::Sell, - sell_amount: U256::from(250u64), - buy_amount: U256::from(500u64), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - }, - // Buy order + (price_query, out_amount) + }; + + // The jit order should fully fill the query + let jit_executed_amount = |side: &dto::Side| -> U256 { + match side { + dto::Side::Sell => 250u64.into(), + dto::Side::Buy => 500u64.into(), + } + }; + + let jit_order = |side: dto::Side| -> dto::JitOrder { dto::JitOrder { - sell_token: buy_token, // Solver sells buy token - buy_token: sell_token, // Solver buys sell token - executed_amount: U256::from(500u64), // Fully fills the query - side: dto::Side::Buy, - sell_amount: U256::from(250u64), - buy_amount: U256::from(500u64), + sell_token: buy_token, // Solver sells buy token + buy_token: sell_token, // Solver buys sell token + executed_amount: jit_executed_amount(&side), // Fully fills the query + side, + sell_amount: 250u64.into(), + buy_amount: 500u64.into(), receiver: H160::zero(), valid_to: 0, app_data: AppDataHash::default(), @@ -895,13 +885,29 @@ mod tests { buy_token_destination: BuyTokenDestination::Erc20, signature: vec![], signing_scheme: SigningScheme::Eip1271, - }, + } + }; + + let test_cases = [ + ( + query_with_out_amount(OrderKind::Sell), + jit_order(dto::Side::Sell), + ), + ( + query_with_out_amount(OrderKind::Buy), + jit_order(dto::Side::Buy), + ), + ( + query_with_out_amount(OrderKind::Sell), + jit_order(dto::Side::Buy), + ), + ( + query_with_out_amount(OrderKind::Buy), + jit_order(dto::Side::Sell), + ), ]; - for (query, jit_order) in queries - .iter() - .flat_map(|query| jit_orders.iter().map(move |jit_order| (query, jit_order))) - { + for ((query, out_amount), jit_order) in test_cases { let trade_kind = TradeKind::Regular(Trade { clearing_prices: clearing_prices.clone(), gas_estimate: Some(50_000), @@ -912,14 +918,6 @@ mod tests { jit_orders: vec![jit_order.clone()], }); - // The out amount is the total amount of buy token the user gets - let out_amount = match (&query.kind, &jit_order.side) { - (OrderKind::Sell, dto::Side::Sell) => U256::from(250u64), - (OrderKind::Buy, dto::Side::Buy) => U256::from(500u64), - (OrderKind::Sell, dto::Side::Buy) => U256::from(250u64), - (OrderKind::Buy, dto::Side::Sell) => U256::from(500u64), - }; - // The Settlement contract is not expected to lose any tokens, but we lose a bit // to test the inaccuracy limit. let tokens_lost = hashmap! { @@ -934,12 +932,12 @@ mod tests { }; // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, query, &trade_kind, &summary); + let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); assert!(matches!(estimate, Err(Error::TooInaccurate))); // The summary has less than 11% inaccuracy let estimate = - ensure_quote_accuracy(&high_threshold, query, &trade_kind, &summary).unwrap(); + ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); assert!(estimate.verified); } } From bea518322ed073ac3ea8afa07f4b382a105473f4 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 7 Nov 2024 12:39:33 +0000 Subject: [PATCH 30/38] Revert balance fetching changes --- crates/shared/src/price_estimation/trade_verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index b00b9dfc3e..ca9352e8b3 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -612,8 +612,8 @@ fn add_balance_queries( let query_balance = solver.methods().store_balance(token, owner, true); let query_balance = Bytes(query_balance.tx.data.unwrap().0); let interaction = (solver.address(), 0.into(), query_balance); - // query balance query at the end of pre-interactions - settlement.interactions[0].push(interaction.clone()); + // query balance right after we receive all `sell_token` + settlement.interactions[1].insert(0, interaction.clone()); // query balance right after we payed out all `buy_token` settlement.interactions[2].insert(0, interaction); settlement From 7b29d96f62e52098ed4b63da218b9db4367de17d Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 7 Nov 2024 14:26:37 +0000 Subject: [PATCH 31/38] Avoid using IR optimizer --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/artifacts/Trader.json | 2 +- crates/contracts/solidity/Makefile | 2 +- crates/contracts/solidity/Solver.sol | 20 +++++++++++++++++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index a021bd0e68..836c3e29a9 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"uint256","name":"sellTokenIndex","type":"uint256"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x6080806040523461001657610a3f908161001c8239f35b600080fdfe60806040526004361015610013575b600080fd5b6000803560e01c9081633bbb2e1d1461003e575063c5746a0f1461003657600080fd5b61000e6102c8565b346101bc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bc57600435610079816101bf565b602435610085816101bf565b61008d6101ec565b9073ffffffffffffffffffffffffffffffffffffffff5a931673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81036100f657506100cc903161088b565b6100d4575080f35b6100ea6100e56100f1925a9061063b565b6108f4565b8254610903565b815580f35b608090817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806101636020957f70a082310000000000000000000000000000000000000000000000000000000084529073ffffffffffffffffffffffffffffffffffffffff60a49216608452565b01915afa80156101af575b839061017e575b6100cc9061088b565b5060203d81116101a8575b6101a1816101996100cc9361049f565b60800161085b565b9050610175565b503d610189565b6101b761054e565b61016e565b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b608435906101ea826101bf565b565b60443590811515820361000e57565b6101043590811515820361000e57565b602435906101ea826101bf565b60c435906101ea826101bf565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020838186019501011161000e57565b9060609160408101918152602092816040858094015285518094520193019160005b8281106102b4575050505090565b8351855293810193928101926001016102a6565b503461000e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57600435610305816101bf565b61030d61020b565b906103166101dd565b67ffffffffffffffff9260a43584811161000e57610338903690600401610225565b610340610218565b9160e43596871161000e5761035c610375973690600401610256565b9590946103676101fb565b9760643591604435916106be565b9061038560405192839283610284565b0390f35b1561039057565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e00000000000000006064820152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9190811015610455575b60051b0190565b61045d610414565b61044e565b3561046c816101bf565b90565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101166080016080811067ffffffffffffffff8211176104e157604052565b6104e961046f565b604052565b67ffffffffffffffff81116104e157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176104e157604052565b600091031261000e57565b506040513d6000823e3d90fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610597575b01160190565b61059f61046f565b610591565b3d156105cf573d906105b58261055b565b916105c36040519384610502565b82523d6000602084013e565b606090565b9291926105e08261055b565b916105ee6040519384610502565b82948184528183011161000e578281602093846000960137010152565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161064857565b6101ea61060b565b6040519081600180549081835260209081840192816000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6926000905b8282106106a6575050505050906101ea910383610502565b8454865288965094850194938301939083019061068e565b9798949a99908b9697949293916106d6303314610389565b610757575b5050505061074992916107396107419261073161074f996107288a8a6000998a8080808073ffffffffffffffffffffffffffffffffffffffff809c165af1506107226105a4565b50610910565b5a9436916105d4565b908816610a09565b505a9061063b565b90549061063b565b94610910565b61046c610650565b73ffffffffffffffffffffffffffffffffffffffff169895509093916107879061078290888d610444565b610462565b92883b1561000e57610731610749968c9660006107399561074f9d61074199838f610816604051978896879586947f57d5a1d3000000000000000000000000000000000000000000000000000000008652600486019293606092919594608085019673ffffffffffffffffffffffffffffffffffffffff93848092168752166020860152604085015216910152565b03925af1801561084e575b610835575b509950839596508194506106db565b80610842610848926104ee565b80610543565b38610826565b61085661054e565b610821565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80602091011261000e5760805190565b600154680100000000000000008110156108e7575b60018101806001558110156108da575b60016000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6108e2610414565b6108b0565b6108ef61046f565b6108a0565b9061116c820180921161064857565b9190820180921161064857565b92919060005b818110610924575050509050565b61092f818387610444565b359061093a826101bf565b303b1561000e576040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529184166024830152600060448301819052600192908160648183305af180156109fc575b6109e9575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109dc575b01610916565b6109e461060b565b6109d6565b806108426109f6926104ee565b386109ad565b610a0461054e565b6109a8565b600091829182602083519301915af190610a216105a4565b9115610a2957565b50602081519101fdfea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361015610013575b600080fd5b6000803560e01c9081633bbb2e1d1461003e575063c5746a0f1461003657600080fd5b61000e6102c8565b346101bc5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101bc57600435610079816101bf565b602435610085816101bf565b61008d6101ec565b9073ffffffffffffffffffffffffffffffffffffffff5a931673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81036100f657506100cc903161088b565b6100d4575080f35b6100ea6100e56100f1925a9061063b565b6108f4565b8254610903565b815580f35b608090817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff806101636020957f70a082310000000000000000000000000000000000000000000000000000000084529073ffffffffffffffffffffffffffffffffffffffff60a49216608452565b01915afa80156101af575b839061017e575b6100cc9061088b565b5060203d81116101a8575b6101a1816101996100cc9361049f565b60800161085b565b9050610175565b503d610189565b6101b761054e565b61016e565b80fd5b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b608435906101ea826101bf565b565b60443590811515820361000e57565b6101043590811515820361000e57565b602435906101ea826101bf565b60c435906101ea826101bf565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020808501948460051b01011161000e57565b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020838186019501011161000e57565b9060609160408101918152602092816040858094015285518094520193019160005b8281106102b4575050505090565b8351855293810193928101926001016102a6565b503461000e576101207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261000e57600435610305816101bf565b61030d61020b565b906103166101dd565b67ffffffffffffffff9260a43584811161000e57610338903690600401610225565b610340610218565b9160e43596871161000e5761035c610375973690600401610256565b9590946103676101fb565b9760643591604435916106be565b9061038560405192839283610284565b0390f35b1561039057565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e00000000000000006064820152fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9190811015610455575b60051b0190565b61045d610414565b61044e565b3561046c816101bf565b90565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09101166080016080811067ffffffffffffffff8211176104e157604052565b6104e961046f565b604052565b67ffffffffffffffff81116104e157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176104e157604052565b600091031261000e57565b506040513d6000823e3d90fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610597575b01160190565b61059f61046f565b610591565b3d156105cf573d906105b58261055b565b916105c36040519384610502565b82523d6000602084013e565b606090565b9291926105e08261055b565b916105ee6040519384610502565b82948184528183011161000e578281602093846000960137010152565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9190820391821161064857565b6101ea61060b565b6040519081600180549081835260209081840192816000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6926000905b8282106106a6575050505050906101ea910383610502565b8454865288965094850194938301939083019061068e565b9798949a99908b9697949293916106d6303314610389565b610757575b5050505061074992916107396107419261073161074f996107288a8a6000998a8080808073ffffffffffffffffffffffffffffffffffffffff809c165af1506107226105a4565b50610910565b5a9436916105d4565b908816610a09565b505a9061063b565b90549061063b565b94610910565b61046c610650565b73ffffffffffffffffffffffffffffffffffffffff169895509093916107879061078290888d610444565b610462565b92883b1561000e57610731610749968c9660006107399561074f9d61074199838f610816604051978896879586947f57d5a1d3000000000000000000000000000000000000000000000000000000008652600486019293606092919594608085019673ffffffffffffffffffffffffffffffffffffffff93848092168752166020860152604085015216910152565b03925af1801561084e575b610835575b509950839596508194506106db565b80610842610848926104ee565b80610543565b38610826565b61085661054e565b610821565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80602091011261000e5760805190565b600154680100000000000000008110156108e7575b60018101806001558110156108da575b60016000527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60155565b6108e2610414565b6108b0565b6108ef61046f565b6108a0565b9061116c820180921161064857565b9190820180921161064857565b92919060005b818110610924575050509050565b61092f818387610444565b359061093a826101bf565b303b1561000e576040517f3bbb2e1d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff92831660048201529184166024830152600060448301819052600192908160648183305af180156109fc575b6109e9575b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146109dc575b01610916565b6109e461060b565b6109d6565b806108426109f6926104ee565b386109ad565b610a0461054e565b6109a8565b600091829182602083519301915af190610a216105a4565b9115610a2957565b50602081519101fdfea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"uint256","name":"sellTokenIndex","type":"uint256"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061097b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063c5746a0f14610050575b600080fd5b61004e610049366004610641565b61007a565b005b61006361005e366004610716565b6101af565b6040516100719291906107f9565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101429190610847565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261088f565b6101939061116c6108a8565b6000808282546101a391906108a8565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8215610326578b73ffffffffffffffffffffffffffffffffffffffff166357d5a1d38e8a8a8f81811061027b5761027b6108bb565b905060200201602081019061029091906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529082166024820152604481018e9052908c166064820152608401600060405180830381600087803b15801561030d57600080fd5b505af1158015610321573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461037c576040519150601f19603f3d011682016040523d82523d6000602084013e610381565b606091505b50505061038f88888f61040a565b61039a8d86866104e2565b91506103a788888f61040a565b60018054806020026020016040519081016040528092919081815260200182805480156103f357602002820191906000526020600020905b8154815260200190600101908083116103df575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d85858481811061042d5761042d6108bb565b905060200201602081019061044291906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104b757600080fd5b505af11580156104cb573d6000803e3d6000fd5b5050505080806104da90610907565b91505061040d565b6000805a905061053f84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610561565b506000545a61054e908361088f565b610558919061088f565b95945050505050565b606061056f83600084610576565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105a0919061093f565b60006040518083038185875af1925050503d80600081146105dd576040519150601f19603f3d011682016040523d82523d6000602084013e6105e2565b606091505b5092509050806105f457815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461061e57600080fd5b50565b803561062c816105fc565b919050565b8035801515811461062c57600080fd5b60008060006060848603121561065657600080fd5b8335610661816105fc565b92506020840135610671816105fc565b915061067f60408501610631565b90509250925092565b60008083601f84011261069a57600080fd5b50813567ffffffffffffffff8111156106b257600080fd5b6020830191508360208260051b85010111156106cd57600080fd5b9250929050565b60008083601f8401126106e657600080fd5b50813567ffffffffffffffff8111156106fe57600080fd5b6020830191508360208285010111156106cd57600080fd5b60008060008060008060008060008060006101208c8e03121561073857600080fd5b6107428c356105fc565b8b359a5061075360208d01356105fc565b60208c0135995060408c0135985060608c0135975061077460808d01610621565b965067ffffffffffffffff8060a08e0135111561079057600080fd5b6107a08e60a08f01358f01610688565b90975095506107b160c08e01610621565b94508060e08e013511156107c457600080fd5b506107d58d60e08e01358e016106d4565b90935091506107e76101008d01610631565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561083a5784518352938301939183019160010161081e565b5090979650505050505050565b60006020828403121561085957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108a2576108a2610860565b92915050565b808201808211156108a2576108a2610860565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108fc57600080fd5b813561056f816105fc565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361093857610938610860565b5060010190565b6000825160005b818110156109605760208186018101518583015201610946565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063c5746a0f14610050575b600080fd5b61004e610049366004610641565b61007a565b005b61006361005e366004610716565b6101af565b6040516100719291906107f9565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101429190610847565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261088f565b6101939061116c6108a8565b6000808282546101a391906108a8565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8215610326578b73ffffffffffffffffffffffffffffffffffffffff166357d5a1d38e8a8a8f81811061027b5761027b6108bb565b905060200201602081019061029091906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529082166024820152604481018e9052908c166064820152608401600060405180830381600087803b15801561030d57600080fd5b505af1158015610321573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461037c576040519150601f19603f3d011682016040523d82523d6000602084013e610381565b606091505b50505061038f88888f61040a565b61039a8d86866104e2565b91506103a788888f61040a565b60018054806020026020016040519081016040528092919081815260200182805480156103f357602002820191906000526020600020905b8154815260200190600101908083116103df575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d85858481811061042d5761042d6108bb565b905060200201602081019061044291906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104b757600080fd5b505af11580156104cb573d6000803e3d6000fd5b5050505080806104da90610907565b91505061040d565b6000805a905061053f84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610561565b506000545a61054e908361088f565b610558919061088f565b95945050505050565b606061056f83600084610576565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105a0919061093f565b60006040518083038185875af1925050503d80600081146105dd576040519150601f19603f3d011682016040523d82523d6000602084013e6105e2565b606091505b5092509050806105f457815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461061e57600080fd5b50565b803561062c816105fc565b919050565b8035801515811461062c57600080fd5b60008060006060848603121561065657600080fd5b8335610661816105fc565b92506020840135610671816105fc565b915061067f60408501610631565b90509250925092565b60008083601f84011261069a57600080fd5b50813567ffffffffffffffff8111156106b257600080fd5b6020830191508360208260051b85010111156106cd57600080fd5b9250929050565b60008083601f8401126106e657600080fd5b50813567ffffffffffffffff8111156106fe57600080fd5b6020830191508360208285010111156106cd57600080fd5b60008060008060008060008060008060006101208c8e03121561073857600080fd5b6107428c356105fc565b8b359a5061075360208d01356105fc565b60208c0135995060408c0135985060608c0135975061077460808d01610621565b965067ffffffffffffffff8060a08e0135111561079057600080fd5b6107a08e60a08f01358f01610688565b90975095506107b160c08e01610621565b94508060e08e013511156107c457600080fd5b506107d58d60e08e01358e016106d4565b90935091506107e76101008d01610631565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561083a5784518352938301939183019160010161081e565b5090979650505050505050565b60006020828403121561085957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108a2576108a2610860565b92915050565b808201808211156108a2576108a2610860565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108fc57600080fd5b813561056f816105fc565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361093857610938610860565b5060010190565b6000825160005b818110156109605760208186018101518583015201610946565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/artifacts/Trader.json b/crates/contracts/artifacts/Trader.json index c6d4e54bf5..f2a600e845 100644 --- a/crates/contracts/artifacts/Trader.json +++ b/crates/contracts/artifacts/Trader.json @@ -1 +1 @@ -{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x6080806040523461001657610b21908161001c8239f35b600080fdfe6080604052600436101561001e575b361561001c5761001c610667565b005b6000803560e01c9081631626ba7e1461004957506357d5a1d30361000e57610044610123565b61000e565b346100f95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100f95760243567ffffffffffffffff8082116100fc57366023830112156100fc5781600401359081116100fc57369101602401116100f9577f1626ba7e000000000000000000000000000000000000000000000000000000006080527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8060a0016080f35b80fd5b8280fd5b73ffffffffffffffffffffffffffffffffffffffff81160361011e57565b600080fd5b503461011e5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011e57600480359061016182610100565b6024359061016e82610100565b604435916064359361017f85610100565b6101bb6101b66101b27f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c973490600182549255565b1590565b6106bb565b73ffffffffffffffffffffffffffffffffffffffff8080961692169180831461044c575b5083926102e79261001c9661028c9316604051907f9b552cc200000000000000000000000000000000000000000000000000000000918281526020978896878381818a8297895afa90811561043f575b600091610422575b50604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152308b820190815273ffffffffffffffffffffffffffffffffffffffff909316602084015293849283920190565b0381895afa908115610415575b6000916103f8575b501061033e575b50506040517f70a08231000000000000000000000000000000000000000000000000000000008152309381019384529485938492508291602090910190565b03915afa918215610331575b600092610304575b50501015610826565b6103239250803d1061032a575b61031b81836105dd565b810190610746565b38806102fb565b503d610311565b610339610755565b6102f3565b61038c9261036960405182815283818981885afa9081156103eb575b6000916103ce575b5086610939565b856040518094819382525afa9081156103c1575b600091610394575b50826109c3565b3880846102a8565b6103b49150853d87116103ba575b6103ac81836105dd565b81019061080e565b38610385565b503d6103a2565b6103c9610755565b61037d565b6103e59150843d86116103ba576103ac81836105dd565b38610362565b6103f3610755565b61035a565b61040f9150833d851161032a5761031b81836105dd565b386102a1565b61041d610755565b610299565b6104399150823d84116103ba576103ac81836105dd565b38610237565b610447610755565b61022f565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152308582019081526020908290819083010381875afa908115610563575b600091610545575b508581106104a6575b506101df565b6104b4908695929395610762565b926104c18447101561079e565b823b1561011e5761001c9686956102e795600061028c9686604051809481937fd0e30db00000000000000000000000000000000000000000000000000000000083525af18015610538575b61051f575b5093509650928194506104a0565b8061052c610532926105a0565b80610803565b38610511565b610540610755565b61050c565b61055d915060203d811161032a5761031b81836105dd565b38610497565b61056b610755565b61048f565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81116105b457604052565b6105bc610570565b604052565b6080810190811067ffffffffffffffff8211176105b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105b457604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff811161065a575b01160190565b610662610570565b610654565b506000806106743661061e565b61068160405191826105dd565b36815260208101903683833782602036830101525190620100005af46106a56108b1565b90156106b357602081519101f35b602081519101fd5b156106c257565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e636500000000000000000000000000000000000000000000000000000000006064820152fd5b9081602091031261011e575190565b506040513d6000823e3d90fd5b9190820391821161076f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b156107a557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152fd5b600091031261011e57565b9081602091031261011e575161082381610100565b90565b1561082d57565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152fd5b3d156108dc573d906108c28261061e565b916108d060405193846105dd565b82523d6000602084013e565b606090565b604051906040820182811067ffffffffffffffff82111761092c575b604052601a82527f5361666545524332303a20617070726f76616c206661696c65640000000000006020830152565b610934610570565b6108fd565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201528260448201526044815261099c816105c1565b5193165af16109a96108b1565b90156106b3576109c1906109bb6108e1565b90610a46565b565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60448201526044815261099c816105c1565b8051908115918215610af1575b505015610a5d5750565b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110610ada575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201610a99565b819250906020918101031261011e5760200151801515810361011e573880610a5356fea164736f6c6343000811000a","deployedBytecode":"0x6080604052600436101561001e575b361561001c5761001c610667565b005b6000803560e01c9081631626ba7e1461004957506357d5a1d30361000e57610044610123565b61000e565b346100f95760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100f95760243567ffffffffffffffff8082116100fc57366023830112156100fc5781600401359081116100fc57369101602401116100f9577f1626ba7e000000000000000000000000000000000000000000000000000000006080527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8060a0016080f35b80fd5b8280fd5b73ffffffffffffffffffffffffffffffffffffffff81160361011e57565b600080fd5b503461011e5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261011e57600480359061016182610100565b6024359061016e82610100565b604435916064359361017f85610100565b6101bb6101b66101b27f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c973490600182549255565b1590565b6106bb565b73ffffffffffffffffffffffffffffffffffffffff8080961692169180831461044c575b5083926102e79261001c9661028c9316604051907f9b552cc200000000000000000000000000000000000000000000000000000000918281526020978896878381818a8297895afa90811561043f575b600091610422575b50604080517fdd62ed3e000000000000000000000000000000000000000000000000000000008152308b820190815273ffffffffffffffffffffffffffffffffffffffff909316602084015293849283920190565b0381895afa908115610415575b6000916103f8575b501061033e575b50506040517f70a08231000000000000000000000000000000000000000000000000000000008152309381019384529485938492508291602090910190565b03915afa918215610331575b600092610304575b50501015610826565b6103239250803d1061032a575b61031b81836105dd565b810190610746565b38806102fb565b503d610311565b610339610755565b6102f3565b61038c9261036960405182815283818981885afa9081156103eb575b6000916103ce575b5086610939565b856040518094819382525afa9081156103c1575b600091610394575b50826109c3565b3880846102a8565b6103b49150853d87116103ba575b6103ac81836105dd565b81019061080e565b38610385565b503d6103a2565b6103c9610755565b61037d565b6103e59150843d86116103ba576103ac81836105dd565b38610362565b6103f3610755565b61035a565b61040f9150833d851161032a5761031b81836105dd565b386102a1565b61041d610755565b610299565b6104399150823d84116103ba576103ac81836105dd565b38610237565b610447610755565b61022f565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152308582019081526020908290819083010381875afa908115610563575b600091610545575b508581106104a6575b506101df565b6104b4908695929395610762565b926104c18447101561079e565b823b1561011e5761001c9686956102e795600061028c9686604051809481937fd0e30db00000000000000000000000000000000000000000000000000000000083525af18015610538575b61051f575b5093509650928194506104a0565b8061052c610532926105a0565b80610803565b38610511565b610540610755565b61050c565b61055d915060203d811161032a5761031b81836105dd565b38610497565b61056b610755565b61048f565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b67ffffffffffffffff81116105b457604052565b6105bc610570565b604052565b6080810190811067ffffffffffffffff8211176105b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105b457604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff811161065a575b01160190565b610662610570565b610654565b506000806106743661061e565b61068160405191826105dd565b36815260208101903683833782602036830101525190620100005af46106a56108b1565b90156106b357602081519101f35b602081519101fd5b156106c257565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e636500000000000000000000000000000000000000000000000000000000006064820152fd5b9081602091031261011e575190565b506040513d6000823e3d90fd5b9190820391821161076f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b156107a557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152fd5b600091031261011e57565b9081602091031261011e575161082381610100565b90565b1561082d57565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152fd5b3d156108dc573d906108c28261061e565b916108d060405193846105dd565b82523d6000602084013e565b606090565b604051906040820182811067ffffffffffffffff82111761092c575b604052601a82527f5361666545524332303a20617070726f76616c206661696c65640000000000006020830152565b610934610570565b6108fd565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201528260448201526044815261099c816105c1565b5193165af16109a96108b1565b90156106b3576109c1906109bb6108e1565b90610a46565b565b600091908291826040519160208301927f095ea7b300000000000000000000000000000000000000000000000000000000845273ffffffffffffffffffffffffffffffffffffffff80921660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60448201526044815261099c816105c1565b8051908115918215610af1575b505015610a5d5750565b604051907f08c379a000000000000000000000000000000000000000000000000000000000825281602080600483015282519283602484015260005b848110610ada575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f836000604480968601015201168101030190fd5b818101830151868201604401528593508201610a99565b819250906020918101031261011e5760200151801515810361011e573880610a5356fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"}],"name":"prepareSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],"bytecode":"0x608060405234801561001057600080fd5b50610bb5806100206000396000f3fe60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","deployedBytecode":"0x60806040526004361061002d5760003560e01c80631626ba7e1461008657806357d5a1d3146100fe57610034565b3661003457005b600061007c6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525062010000939250506101209050565b9050805160208201f35b34801561009257600080fd5b506100c96100a136600461098b565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010a57600080fd5b5061011e610119366004610a2c565b6101a2565b005b606060008373ffffffffffffffffffffffffffffffffffffffff16836040516101499190610aa3565b600060405180830381855af49150503d8060008114610184576040519150601f19603f3d011682016040523d82523d6000602084013e610189565b606091505b50925090508061019b57815160208301fd5b5092915050565b6101aa61077f565b1561023c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036103e7576040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8516906370a0823190602401602060405180830381865afa1580156102dc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103009190610abf565b9050828110156103e55760006103168285610ad8565b905080471015610382576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f7420656e6f7567682045544820746f2077726170000000000000000000006044820152606401610233565b8273ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103ca57600080fd5b505af11580156103de573d6000803e3d6000fd5b5050505050505b505b60008373ffffffffffffffffffffffffffffffffffffffff1663dd62ed3e308773ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610451573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104759190610b18565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff928316600482015291166024820152604401602060405180830381865afa1580156104e5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105099190610abf565b905082811015610654576105a48573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105859190610b18565b73ffffffffffffffffffffffffffffffffffffffff86169060006107ba565b6106548573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106169190610b18565b73ffffffffffffffffffffffffffffffffffffffff8616907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6107ba565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa1580156106c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e59190610abf565b905083811015610777576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f5f746f6b656e00000000000000000000000000000000000000000000000000006064820152608401610233565b505050505050565b6000806107ad60017f7f36ecad6e52bbe2ff70badce94360882c890b7877b16131c08eabfc635c9735610ad8565b8054600190915592915050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b3000000000000000000000000000000000000000000000000000000001790529060009061084d90861683610895565b905061088e816040518060400160405280601a81526020017f5361666545524332303a20617070726f76616c206661696c65640000000000008152506108aa565b5050505050565b60606108a383600084610905565b9392505050565b815115806108c75750818060200190518101906108c79190610b35565b8190610900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102339190610b57565b505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161092f9190610aa3565b60006040518083038185875af1925050503d806000811461096c576040519150601f19603f3d011682016040523d82523d6000602084013e610971565b606091505b50925090508061098357815160208301fd5b509392505050565b6000806000604084860312156109a057600080fd5b83359250602084013567ffffffffffffffff808211156109bf57600080fd5b818601915086601f8301126109d357600080fd5b8135818111156109e257600080fd5b8760208285010111156109f457600080fd5b6020830194508093505050509250925092565b73ffffffffffffffffffffffffffffffffffffffff81168114610a2957600080fd5b50565b60008060008060808587031215610a4257600080fd5b8435610a4d81610a07565b93506020850135610a5d81610a07565b9250604085013591506060850135610a7481610a07565b939692955090935050565b60005b83811015610a9a578181015183820152602001610a82565b50506000910152565b60008251610ab5818460208701610a7f565b9190910192915050565b600060208284031215610ad157600080fd5b5051919050565b81810381811115610b12577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b600060208284031215610b2a57600080fd5b81516108a381610a07565b600060208284031215610b4757600080fd5b815180151581146108a357600080fd5b6020815260008251806020840152610b76816040850160208701610a7f565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016040019291505056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Makefile b/crates/contracts/solidity/Makefile index b12596d025..5816391d8d 100644 --- a/crates/contracts/solidity/Makefile +++ b/crates/contracts/solidity/Makefile @@ -2,7 +2,7 @@ DOCKER := docker JQ := jq SOLC := ethereum/solc:0.8.17 -SOLFLAGS := --overwrite --abi --bin --bin-runtime --metadata-hash none --optimize --optimize-runs 1000000 --via-ir +SOLFLAGS := --overwrite --abi --bin --bin-runtime --metadata-hash none --optimize --optimize-runs 1000000 TARGETDIR := ../../../target/solidity ARTIFACTDIR := ../artifacts diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index a912918f9f..225e5c57ee 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -79,9 +79,7 @@ contract Solver { // Store pre-settlement balances _storeSettlementBalances(tokens, settlementContract); - uint256 gasStart = gasleft(); - address(settlementContract).doCall(settlementCall); - gasUsed = gasStart - gasleft() - _simulationOverhead; + gasUsed = _executeSettlement(address(settlementContract), settlementCall); // Store post-settlement balances _storeSettlementBalances(tokens, settlementContract); @@ -108,9 +106,25 @@ contract Solver { } } + /// @dev Helper function that reads and stores the balances of the `settlementContract` for each token in `tokens`. + /// @param tokens - list of tokens used in the trade + /// @param settlementContract - the settlement contract whose balances are being read function _storeSettlementBalances(address[] calldata tokens, ISettlement settlementContract) internal { for (uint256 i = 0; i < tokens.length; i++) { this.storeBalance(tokens[i], address(settlementContract), false); } } + + /// @dev Executes the settlement and measures the gas used. + /// @param settlementContract The address of the settlement contract. + /// @param settlementCall The calldata for the settlement function. + /// @return gasUsed The amount of gas used during the settlement execution. + function _executeSettlement( + address settlementContract, + bytes calldata settlementCall + ) private returns (uint256 gasUsed) { + uint256 gasStart = gasleft(); + address(settlementContract).doCall(settlementCall); + gasUsed = gasStart - gasleft() - _simulationOverhead; + } } From 0be281499a3af31d33f89882ae4b201dfade3b46 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 18 Nov 2024 11:54:08 +0000 Subject: [PATCH 32/38] Revert "Revert balance fetching changes" This reverts commit bea518322ed073ac3ea8afa07f4b382a105473f4. --- crates/shared/src/price_estimation/trade_verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index ca9352e8b3..b00b9dfc3e 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -612,8 +612,8 @@ fn add_balance_queries( let query_balance = solver.methods().store_balance(token, owner, true); let query_balance = Bytes(query_balance.tx.data.unwrap().0); let interaction = (solver.address(), 0.into(), query_balance); - // query balance right after we receive all `sell_token` - settlement.interactions[1].insert(0, interaction.clone()); + // query balance query at the end of pre-interactions + settlement.interactions[0].push(interaction.clone()); // query balance right after we payed out all `buy_token` settlement.interactions[2].insert(0, interaction); settlement From 277b4e343d4935b53e68f8198c6adebad190543a Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 18 Nov 2024 12:18:48 +0000 Subject: [PATCH 33/38] Adjust balance fetching --- .../shared/src/price_estimation/trade_verifier.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index b00b9dfc3e..b39b364081 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -133,13 +133,7 @@ impl TradeVerifier { &self.domain_separator, )?; - let settlement = add_balance_queries( - settlement, - query, - verification, - self.settlement.address(), - &solver, - ); + let settlement = add_balance_queries(settlement, query, verification, &solver); let settlement = self .settlement @@ -592,7 +586,6 @@ fn add_balance_queries( mut settlement: EncodedSettlement, query: &PriceQuery, verification: &Verification, - settlement_contract: H160, solver: &Solver, ) -> EncodedSettlement { let (token, owner) = match query.kind { @@ -606,8 +599,8 @@ fn add_balance_queries( (query.buy_token, receiver) } - // track how much `sell_token` the settlement contract actually spent - OrderKind::Buy => (query.sell_token, settlement_contract), + // track how much `sell_token` the `from` address actually spent + OrderKind::Buy => (query.sell_token, verification.from), }; let query_balance = solver.methods().store_balance(token, owner, true); let query_balance = Bytes(query_balance.tx.data.unwrap().0); From f1e18065f3a9ba84e1e20951e3330a27faa57019 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 28 Nov 2024 09:56:59 +0000 Subject: [PATCH 34/38] Pr comments --- crates/contracts/artifacts/Solver.json | 2 +- crates/contracts/solidity/Solver.sol | 6 +++--- crates/shared/src/price_estimation/trade_verifier.rs | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/contracts/artifacts/Solver.json b/crates/contracts/artifacts/Solver.json index 836c3e29a9..f4de20dccc 100644 --- a/crates/contracts/artifacts/Solver.json +++ b/crates/contracts/artifacts/Solver.json @@ -1 +1 @@ -{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"uint256","name":"sellTokenIndex","type":"uint256"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b5061097b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063c5746a0f14610050575b600080fd5b61004e610049366004610641565b61007a565b005b61006361005e366004610716565b6101af565b6040516100719291906107f9565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101429190610847565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261088f565b6101939061116c6108a8565b6000808282546101a391906108a8565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8215610326578b73ffffffffffffffffffffffffffffffffffffffff166357d5a1d38e8a8a8f81811061027b5761027b6108bb565b905060200201602081019061029091906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529082166024820152604481018e9052908c166064820152608401600060405180830381600087803b15801561030d57600080fd5b505af1158015610321573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461037c576040519150601f19603f3d011682016040523d82523d6000602084013e610381565b606091505b50505061038f88888f61040a565b61039a8d86866104e2565b91506103a788888f61040a565b60018054806020026020016040519081016040528092919081815260200182805480156103f357602002820191906000526020600020905b8154815260200190600101908083116103df575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d85858481811061042d5761042d6108bb565b905060200201602081019061044291906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104b757600080fd5b505af11580156104cb573d6000803e3d6000fd5b5050505080806104da90610907565b91505061040d565b6000805a905061053f84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610561565b506000545a61054e908361088f565b610558919061088f565b95945050505050565b606061056f83600084610576565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105a0919061093f565b60006040518083038185875af1925050503d80600081146105dd576040519150601f19603f3d011682016040523d82523d6000602084013e6105e2565b606091505b5092509050806105f457815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461061e57600080fd5b50565b803561062c816105fc565b919050565b8035801515811461062c57600080fd5b60008060006060848603121561065657600080fd5b8335610661816105fc565b92506020840135610671816105fc565b915061067f60408501610631565b90509250925092565b60008083601f84011261069a57600080fd5b50813567ffffffffffffffff8111156106b257600080fd5b6020830191508360208260051b85010111156106cd57600080fd5b9250929050565b60008083601f8401126106e657600080fd5b50813567ffffffffffffffff8111156106fe57600080fd5b6020830191508360208285010111156106cd57600080fd5b60008060008060008060008060008060006101208c8e03121561073857600080fd5b6107428c356105fc565b8b359a5061075360208d01356105fc565b60208c0135995060408c0135985060608c0135975061077460808d01610621565b965067ffffffffffffffff8060a08e0135111561079057600080fd5b6107a08e60a08f01358f01610688565b90975095506107b160c08e01610621565b94508060e08e013511156107c457600080fd5b506107d58d60e08e01358e016106d4565b90935091506107e76101008d01610631565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561083a5784518352938301939183019160010161081e565b5090979650505050505050565b60006020828403121561085957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108a2576108a2610860565b92915050565b808201808211156108a2576108a2610860565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108fc57600080fd5b813561056f816105fc565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361093857610938610860565b5060010190565b6000825160005b818110156109605760208186018101518583015201610946565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063c5746a0f14610050575b600080fd5b61004e610049366004610641565b61007a565b005b61006361005e366004610716565b6101af565b6040516100719291906107f9565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101429190610847565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a610187908261088f565b6101939061116c6108a8565b6000808282546101a391906108a8565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b8215610326578b73ffffffffffffffffffffffffffffffffffffffff166357d5a1d38e8a8a8f81811061027b5761027b6108bb565b905060200201602081019061029091906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff92831660048201529082166024820152604481018e9052908c166064820152608401600060405180830381600087803b15801561030d57600080fd5b505af1158015610321573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461037c576040519150601f19603f3d011682016040523d82523d6000602084013e610381565b606091505b50505061038f88888f61040a565b61039a8d86866104e2565b91506103a788888f61040a565b60018054806020026020016040519081016040528092919081815260200182805480156103f357602002820191906000526020600020905b8154815260200190600101908083116103df575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d85858481811061042d5761042d6108bb565b905060200201602081019061044291906108ea565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b1580156104b757600080fd5b505af11580156104cb573d6000803e3d6000fd5b5050505080806104da90610907565b91505061040d565b6000805a905061053f84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610561565b506000545a61054e908361088f565b610558919061088f565b95945050505050565b606061056f83600084610576565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff1684846040516105a0919061093f565b60006040518083038185875af1925050503d80600081146105dd576040519150601f19603f3d011682016040523d82523d6000602084013e6105e2565b606091505b5092509050806105f457815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461061e57600080fd5b50565b803561062c816105fc565b919050565b8035801515811461062c57600080fd5b60008060006060848603121561065657600080fd5b8335610661816105fc565b92506020840135610671816105fc565b915061067f60408501610631565b90509250925092565b60008083601f84011261069a57600080fd5b50813567ffffffffffffffff8111156106b257600080fd5b6020830191508360208260051b85010111156106cd57600080fd5b9250929050565b60008083601f8401126106e657600080fd5b50813567ffffffffffffffff8111156106fe57600080fd5b6020830191508360208285010111156106cd57600080fd5b60008060008060008060008060008060006101208c8e03121561073857600080fd5b6107428c356105fc565b8b359a5061075360208d01356105fc565b60208c0135995060408c0135985060608c0135975061077460808d01610621565b965067ffffffffffffffff8060a08e0135111561079057600080fd5b6107a08e60a08f01358f01610688565b90975095506107b160c08e01610621565b94508060e08e013511156107c457600080fd5b506107d58d60e08e01358e016106d4565b90935091506107e76101008d01610631565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b8181101561083a5784518352938301939183019160010161081e565b5090979650505050505050565b60006020828403121561085957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156108a2576108a2610860565b92915050565b808201808211156108a2576108a2610860565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108fc57600080fd5b813561056f816105fc565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361093857610938610860565b5060010190565b6000825160005b818110156109605760208186018101518583015201610946565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} +{"abi":[{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"countGas","type":"bool"}],"name":"storeBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISettlement","name":"settlementContract","type":"address"},{"internalType":"address payable","name":"trader","type":"address"},{"internalType":"address","name":"sellToken","type":"address"},{"internalType":"uint256","name":"sellAmount","type":"uint256"},{"internalType":"address","name":"nativeToken","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"address payable","name":"receiver","type":"address"},{"internalType":"bytes","name":"settlementCall","type":"bytes"},{"internalType":"bool","name":"mockPreconditions","type":"bool"}],"name":"swap","outputs":[{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256[]","name":"queriedBalances","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":"0x608060405234801561001057600080fd5b50610941806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","deployedBytecode":"0x608060405234801561001057600080fd5b50600436106100365760003560e01c80633bbb2e1d1461003b578063cc6b67a914610050575b600080fd5b61004e610049366004610600565b61007a565b005b61006361005e3660046106d5565b6101af565b6040516100719291906107bf565b60405180910390f35b60005a9050600173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee73ffffffffffffffffffffffffffffffffffffffff861614610147576040517f70a0823100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301528616906370a0823190602401602060405180830381865afa15801561011e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610142919061080d565b610160565b8373ffffffffffffffffffffffffffffffffffffffff16315b8154600181018355600092835260209092209091015581156101a9575a6101879082610855565b6101939061116c61086e565b6000808282546101a3919061086e565b90915550505b50505050565b60006060333014610246576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603860248201527f6f6e6c792073696d756c6174696f6e206c6f67696320697320616c6c6f77656460448201527f20746f2063616c6c202773776170272066756e6374696f6e0000000000000000606482015260840160405180910390fd5b82156102e5576040517f57d5a1d300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8e811660048301528c81166024830152604482018c90528a811660648301528d16906357d5a1d390608401600060405180830381600087803b1580156102cc57600080fd5b505af11580156102e0573d6000803e3d6000fd5b505050505b60405173ffffffffffffffffffffffffffffffffffffffff8716906000908181818181875af1925050503d806000811461033b576040519150601f19603f3d011682016040523d82523d6000602084013e610340565b606091505b50505061034e88888f6103c9565b6103598d86866104a1565b915061036688888f6103c9565b60018054806020026020016040519081016040528092919081815260200182805480156103b257602002820191906000526020600020905b81548152602001906001019080831161039e575b505050505090509b509b9950505050505050505050565b60005b828110156101a95730633bbb2e1d8585848181106103ec576103ec610881565b905060200201602081019061040191906108b0565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9182166004820152908516602482015260006044820152606401600060405180830381600087803b15801561047657600080fd5b505af115801561048a573d6000803e3d6000fd5b505050508080610499906108cd565b9150506103cc565b6000805a90506104fe84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505073ffffffffffffffffffffffffffffffffffffffff891692915050610520565b506000545a61050d9083610855565b6105179190610855565b95945050505050565b606061052e83600084610535565b9392505050565b606060008473ffffffffffffffffffffffffffffffffffffffff16848460405161055f9190610905565b60006040518083038185875af1925050503d806000811461059c576040519150601f19603f3d011682016040523d82523d6000602084013e6105a1565b606091505b5092509050806105b357815160208301fd5b509392505050565b73ffffffffffffffffffffffffffffffffffffffff811681146105dd57600080fd5b50565b80356105eb816105bb565b919050565b803580151581146105eb57600080fd5b60008060006060848603121561061557600080fd5b8335610620816105bb565b92506020840135610630816105bb565b915061063e604085016105f0565b90509250925092565b60008083601f84011261065957600080fd5b50813567ffffffffffffffff81111561067157600080fd5b6020830191508360208260051b850101111561068c57600080fd5b9250929050565b60008083601f8401126106a557600080fd5b50813567ffffffffffffffff8111156106bd57600080fd5b60208301915083602082850101111561068c57600080fd5b60008060008060008060008060008060006101208c8e0312156106f757600080fd5b6107018c356105bb565b8b359a5061071260208d01356105bb565b60208c0135995061072560408d016105e0565b985060608c0135975061073a60808d016105e0565b965067ffffffffffffffff8060a08e0135111561075657600080fd5b6107668e60a08f01358f01610647565b909750955061077760c08e016105e0565b94508060e08e0135111561078a57600080fd5b5061079b8d60e08e01358e01610693565b90935091506107ad6101008d016105f0565b90509295989b509295989b9093969950565b6000604082018483526020604081850152818551808452606086019150828701935060005b81811015610800578451835293830193918301916001016107e4565b5090979650505050505050565b60006020828403121561081f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561086857610868610826565b92915050565b8082018082111561086857610868610826565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156108c257600080fd5b813561052e816105bb565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036108fe576108fe610826565b5060010190565b6000825160005b81811015610926576020818601810151858301520161090c565b50600092019182525091905056fea164736f6c6343000811000a","devdoc":{"methods":{}},"userdoc":{"methods":{}}} diff --git a/crates/contracts/solidity/Solver.sol b/crates/contracts/solidity/Solver.sol index 225e5c57ee..9833c1e5c1 100644 --- a/crates/contracts/solidity/Solver.sol +++ b/crates/contracts/solidity/Solver.sol @@ -29,7 +29,7 @@ contract Solver { /// @param settlementContract - address of the settlement contract because /// it does not have a stable address in tests. /// @param trader - address of the order owner doing the trade - /// @param sellTokenIndex - index in the tokens array of the token being sold + /// @param sellToken - address of the token being sold /// @param sellAmount - amount being sold /// @param nativeToken - ERC20 version of the chain's token /// @param tokens - list of tokens used in the trade @@ -45,7 +45,7 @@ contract Solver { function swap( ISettlement settlementContract, address payable trader, - uint256 sellTokenIndex, + address sellToken, uint256 sellAmount, address nativeToken, address[] calldata tokens, @@ -64,7 +64,7 @@ contract Solver { Trader(trader) .prepareSwap( settlementContract, - tokens[sellTokenIndex], + sellToken, sellAmount, nativeToken ); diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index b39b364081..545d9ea9e5 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -156,11 +156,7 @@ impl TradeVerifier { .swap( self.settlement.address(), verification.from, - tokens - .iter() - .position(|&t| t == query.sell_token) - .context("missing query sell token")? - .into(), + query.sell_token, sell_amount, self.native_token, tokens.clone(), @@ -634,6 +630,8 @@ impl SettleOutput { let tokens = function.decode_output(output).context("decode")?; let (gas_used, balances): (U256, Vec) = Tokenize::from_token(Token::Tuple(tokens))?; + // The balances are stored in the following order: + // [...tokens_before, user_balance_before, user_balance_after, ...tokens_after] let mut i = 0; let mut tokens_lost = HashMap::new(); // Get settlement contract balances before the trade From 8718d83ff8b16c0297347996080cb68b456e8c03 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 28 Nov 2024 10:01:58 +0000 Subject: [PATCH 35/38] Ceil div --- crates/shared/src/trade_finding.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/shared/src/trade_finding.rs b/crates/shared/src/trade_finding.rs index 3aad80630a..d086e61c69 100644 --- a/crates/shared/src/trade_finding.rs +++ b/crates/shared/src/trade_finding.rs @@ -161,11 +161,13 @@ impl Trade { OrderKind::Sell => order_amount .mul(&sell_price) .checked_div(&buy_price) - .context("div by zero: buy price")?, + .context("div by zero: buy price")? + .ceil(), OrderKind::Buy => order_amount .mul(&buy_price) .checked_div(&sell_price) - .context("div by zero: sell price")?, + .context("div by zero: sell price")? + .ceil(), }; big_rational_to_u256(&out_amount).context("out amount is not a valid U256") From 8324c32e45b836f1f03c2949037deb7cb70f5cde Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 28 Nov 2024 10:45:29 +0000 Subject: [PATCH 36/38] Redundant ceil --- crates/shared/src/trade_finding/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index d086e61c69..e9d7e4a17c 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -162,12 +162,11 @@ impl Trade { .mul(&sell_price) .checked_div(&buy_price) .context("div by zero: buy price")? - .ceil(), + .ceil(), /* `ceil` is used to compute buy amount only: https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol#L389-L411 */ OrderKind::Buy => order_amount .mul(&buy_price) .checked_div(&sell_price) - .context("div by zero: sell price")? - .ceil(), + .context("div by zero: sell price")?, }; big_rational_to_u256(&out_amount).context("out amount is not a valid U256") From 9b0e9066945055b43e6a8cc142269557290854d2 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 2 Dec 2024 10:31:49 +0000 Subject: [PATCH 37/38] Redundant changes --- .../src/price_estimation/trade_verifier.rs | 178 +++++------------- 1 file changed, 52 insertions(+), 126 deletions(-) diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 545d9ea9e5..02e20a24d9 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -266,7 +266,12 @@ impl TradeVerifier { "verified quote", ); - ensure_quote_accuracy(&self.quote_inaccuracy_limit, query, trade, &summary) + ensure_quote_accuracy( + &self.quote_inaccuracy_limit, + query, + trade.solver(), + &summary, + ) } /// Configures all the state overrides that are needed to mock the given @@ -675,7 +680,7 @@ impl SettleOutput { fn ensure_quote_accuracy( inaccuracy_limit: &BigRational, query: &PriceQuery, - trade: &TradeKind, + solver: H160, summary: &SettleOutput, ) -> std::result::Result { // amounts verified by the simulation @@ -706,7 +711,7 @@ fn ensure_quote_accuracy( Ok(Estimate { out_amount: summary.out_amount, gas: summary.gas_used.as_u64(), - solver: trade.solver(), + solver, verified: true, }) } @@ -733,13 +738,7 @@ enum Error { #[cfg(test)] mod tests { - use { - super::*, - crate::trade_finding::Trade, - app_data::AppDataHash, - model::order::{BuyTokenDestination, SellTokenSource}, - std::str::FromStr, - }; + use {super::*, std::str::FromStr}; #[test] fn discards_inaccurate_quotes() { @@ -758,6 +757,43 @@ mod tests { buy_token, }; + // buy token is lost + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(500.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); + + // sell token is lost + let tokens_lost = hashmap! { + buy_token => BigRational::from_integer(0.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(matches!(estimate, Err(Error::SimulationFailed(_)))); + + // everything is in-place + let tokens_lost = hashmap! { + sell_token => BigRational::from_integer(400.into()), + buy_token => BigRational::from_integer(0.into()), + }; + let summary = SettleOutput { + gas_used: 0.into(), + out_amount: 2_000.into(), + tokens_lost, + }; + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &summary); + assert!(estimate.is_ok()); + let tokens_lost = hashmap! { sell_token => BigRational::from_integer(500.into()), buy_token => BigRational::from_integer(0.into()), @@ -769,12 +805,11 @@ mod tests { tokens_lost, }; - let trade = TradeKind::Legacy(Default::default()); - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &sell_more); + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &sell_more); + let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &sell_more); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -788,11 +823,11 @@ mod tests { tokens_lost, }; - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_more); + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_more); assert!(matches!(estimate, Err(Error::TooInaccurate))); // passes with slightly higher tolerance - let estimate = ensure_quote_accuracy(&high_threshold, &query, &trade, &pay_out_more); + let estimate = ensure_quote_accuracy(&high_threshold, &query, H160::zero(), &pay_out_more); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -806,7 +841,7 @@ mod tests { tokens_lost, }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &sell_less); + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &sell_less); assert!(estimate.is_ok()); let tokens_lost = hashmap! { @@ -820,116 +855,7 @@ mod tests { tokens_lost, }; // Ending up with surplus in the buffers is always fine - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade, &pay_out_less); + let estimate = ensure_quote_accuracy(&low_threshold, &query, H160::zero(), &pay_out_less); assert!(estimate.is_ok()); } - - #[test] - fn ensure_quote_accuracy_with_jit_orders() { - // Inaccuracy limit of 10% - let low_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.1").unwrap()); - let high_threshold = big_decimal_to_big_rational(&BigDecimal::from_str("0.11").unwrap()); - - let sell_token: H160 = H160::from_low_u64_be(1); - let buy_token: H160 = H160::from_low_u64_be(2); - - let mut clearing_prices = HashMap::new(); - clearing_prices.insert(sell_token, U256::from(1u64)); - clearing_prices.insert(buy_token, U256::from(2u64)); - - let query_with_out_amount = |kind: OrderKind| -> (PriceQuery, U256) { - let (in_amount, out_amount) = match kind { - OrderKind::Sell => (500u64.into(), 250u64.into()), - OrderKind::Buy => (250u64.into(), 500u64.into()), - }; - let price_query = PriceQuery { - in_amount: NonZeroU256::new(in_amount).unwrap(), - kind, - sell_token, - buy_token, - }; - - (price_query, out_amount) - }; - - // The jit order should fully fill the query - let jit_executed_amount = |side: &dto::Side| -> U256 { - match side { - dto::Side::Sell => 250u64.into(), - dto::Side::Buy => 500u64.into(), - } - }; - - let jit_order = |side: dto::Side| -> dto::JitOrder { - dto::JitOrder { - sell_token: buy_token, // Solver sells buy token - buy_token: sell_token, // Solver buys sell token - executed_amount: jit_executed_amount(&side), // Fully fills the query - side, - sell_amount: 250u64.into(), - buy_amount: 500u64.into(), - receiver: H160::zero(), - valid_to: 0, - app_data: AppDataHash::default(), - partially_fillable: false, - sell_token_source: SellTokenSource::Erc20, - buy_token_destination: BuyTokenDestination::Erc20, - signature: vec![], - signing_scheme: SigningScheme::Eip1271, - } - }; - - let test_cases = [ - ( - query_with_out_amount(OrderKind::Sell), - jit_order(dto::Side::Sell), - ), - ( - query_with_out_amount(OrderKind::Buy), - jit_order(dto::Side::Buy), - ), - ( - query_with_out_amount(OrderKind::Sell), - jit_order(dto::Side::Buy), - ), - ( - query_with_out_amount(OrderKind::Buy), - jit_order(dto::Side::Sell), - ), - ]; - - for ((query, out_amount), jit_order) in test_cases { - let trade_kind = TradeKind::Regular(Trade { - clearing_prices: clearing_prices.clone(), - gas_estimate: Some(50_000), - pre_interactions: vec![], - interactions: vec![], - solver: H160::from_low_u64_be(0x1234), - tx_origin: None, - jit_orders: vec![jit_order.clone()], - }); - - // The Settlement contract is not expected to lose any tokens, but we lose a bit - // to test the inaccuracy limit. - let tokens_lost = hashmap! { - sell_token => BigRational::from_integer(50.into()), - buy_token => BigRational::from_integer(25.into()), - }; - - let summary = SettleOutput { - gas_used: U256::from(50_000u64), - out_amount, - tokens_lost, - }; - - // The summary has 10% inaccuracy - let estimate = ensure_quote_accuracy(&low_threshold, &query, &trade_kind, &summary); - assert!(matches!(estimate, Err(Error::TooInaccurate))); - - // The summary has less than 11% inaccuracy - let estimate = - ensure_quote_accuracy(&high_threshold, &query, &trade_kind, &summary).unwrap(); - assert!(estimate.verified); - } - } } From fc0ad49598627014fdec2529a1c87292eac6512f Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 2 Dec 2024 10:35:42 +0000 Subject: [PATCH 38/38] Comment --- crates/shared/src/trade_finding/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/shared/src/trade_finding/mod.rs b/crates/shared/src/trade_finding/mod.rs index 8906a45b4b..75096282b0 100644 --- a/crates/shared/src/trade_finding/mod.rs +++ b/crates/shared/src/trade_finding/mod.rs @@ -127,6 +127,8 @@ pub struct Trade { pub clearing_prices: HashMap, /// How many units of gas this trade will roughly cost. pub gas_estimate: Option, + /// The onchain calls to run before sending user funds to the settlement + /// contract. pub pre_interactions: Vec, /// Interactions needed to produce the expected trade amount. pub interactions: Vec,