Skip to content

Commit

Permalink
Support JIT orders in the trade verifier (#3085)
Browse files Browse the repository at this point in the history
# Description
Closes task n2 from #3082
by implementing support of the quotes with JIT orders in the trade
verifier. For a gradual migration, it has to support both quote
versions.

# Changes

- [ ] Added a new version of the trade with JIT orders.
- [ ] Utilized the clearing prices to calculate the out amount.
- [ ] Altered the `Solver.sol` helper contract to fetch all token
balances as was proposed in [one of the
comments](#3085 (comment)),
which reduces the overall code complexity.
- [ ] Bumped into an issue while converting floats into `BigRational`.
~~Implemented a workaround with converting float's string representation
into `BigRational`.~~ Used `BigDecimal` in the config instead.

## How to test
Unit tests. e2e would be possible only once the driver support is
implemented(see #3103).
  • Loading branch information
squadgazzz authored Dec 2, 2024
1 parent 69a68bf commit eb98aaf
Show file tree
Hide file tree
Showing 12 changed files with 617 additions and 164 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,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
Expand Down
2 changes: 1 addition & 1 deletion crates/contracts/artifacts/Solver.json

Large diffs are not rendered by default.

41 changes: 32 additions & 9 deletions crates/contracts/solidity/Solver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ contract Solver {
/// @param trader - address of the order owner doing the trade
/// @param sellToken - address 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
Expand All @@ -47,8 +47,8 @@ contract Solver {
address payable trader,
address sellToken,
uint256 sellAmount,
address buyToken,
address nativeToken,
address[] calldata tokens,
address payable receiver,
bytes calldata settlementCall,
bool mockPreconditions
Expand Down Expand Up @@ -76,13 +76,14 @@ contract Solver {
// contract.
receiver.call{value: 0}("");

this.storeBalance(sellToken, address(settlementContract), false);
this.storeBalance(buyToken, address(settlementContract), false);
uint256 gasStart = gasleft();
address(settlementContract).doCall(settlementCall);
gasUsed = gasStart - gasleft() - _simulationOverhead;
this.storeBalance(sellToken, address(settlementContract), false);
this.storeBalance(buyToken, address(settlementContract), false);
// Store pre-settlement balances
_storeSettlementBalances(tokens, settlementContract);

gasUsed = _executeSettlement(address(settlementContract), settlementCall);

// Store post-settlement balances
_storeSettlementBalances(tokens, settlementContract);

queriedBalances = _queriedBalances;
}

Expand All @@ -104,4 +105,26 @@ contract Solver {
_simulationOverhead += gasStart - gasleft() + 4460;
}
}

/// @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;
}
}
1 change: 1 addition & 0 deletions crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ app-data = { path = "../app-data" }
anyhow = { workspace = true }
autopilot = { path = "../autopilot" }
axum = { workspace = true }
bigdecimal = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
contracts = { path = "../contracts" }
Expand Down
13 changes: 8 additions & 5 deletions crates/e2e/tests/e2e/quote_verification.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use {
bigdecimal::{BigDecimal, Zero},
e2e::setup::*,
ethcontract::H160,
ethrpc::Web3,
Expand All @@ -13,7 +14,7 @@ use {
Estimate,
Verification,
},
trade_finding::{Interaction, Trade},
trade_finding::{Interaction, LegacyTrade, TradeKind},
},
std::{str::FromStr, sync::Arc},
};
Expand Down Expand Up @@ -61,8 +62,10 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) {
block_stream,
onchain.contracts().gp_settlement.address(),
onchain.contracts().weth.address(),
0.0,
);
BigDecimal::zero(),
)
.await
.unwrap();

let verify_trade = |tx_origin| {
let verifier = verifier.clone();
Expand All @@ -86,7 +89,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) {
sell_token_source: SellTokenSource::Erc20,
buy_token_destination: BuyTokenDestination::Erc20,
},
Trade {
TradeKind::Legacy(LegacyTrade {
out_amount: 16380122291179526144u128.into(),
gas_estimate: Some(225000),
interactions: vec![Interaction {
Expand All @@ -98,7 +101,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) {
solver: H160::from_str("0xe3067c7c27c1038de4e8ad95a83b927d23dfbd99")
.unwrap(),
tx_origin,
},
}),
)
.await
}
Expand Down
1 change: 1 addition & 0 deletions crates/orderbook/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,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
Expand Down
31 changes: 18 additions & 13 deletions crates/shared/src/price_estimation/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,28 +73,30 @@ 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<Self> {
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,
estimators: HashMap::new(),
})
}

fn trade_verifier(
async fn trade_verifier(
args: &'a Arguments,
shared_args: &arguments::Arguments,
network: &Network,
components: &Components,
) -> Option<Arc<dyn TradeVerifying>> {
let web3 = network.simulation_web3.clone()?;
) -> Result<Option<Arc<dyn TradeVerifying>>> {
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
Expand All @@ -111,14 +113,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.clone(),
)
.await?,
)))
}

Expand Down
3 changes: 2 additions & 1 deletion crates/shared/src/price_estimation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use {
trade_finding::Interaction,
},
anyhow::Result,
bigdecimal::BigDecimal,
ethcontract::{H160, U256},
futures::future::BoxFuture,
itertools::Itertools,
Expand Down Expand Up @@ -192,7 +193,7 @@ pub struct Arguments {
/// 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,
pub quote_inaccuracy_limit: BigDecimal,

/// How strict quote verification should be.
#[clap(
Expand Down
Loading

0 comments on commit eb98aaf

Please sign in to comment.