Skip to content

Commit

Permalink
Enable Auto-detection of Token Balance Overrides
Browse files Browse the repository at this point in the history
This PR is a follow up to #3 in that it adds configuration and glue code
for enabling the automatic token balance override detection introduced
in the aforementioned PR.

An E2E test was modified to include balance override tests for both
configured and auto-detected tokens.
  • Loading branch information
nlordell committed Dec 4, 2024
1 parent 0e32024 commit 2be21cf
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 120 deletions.
2 changes: 1 addition & 1 deletion crates/contracts/artifacts/Trader.json

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions crates/contracts/solidity/Trader.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ contract Trader {
uint256 availableNativeToken = IERC20(sellToken).balanceOf(address(this));
if (availableNativeToken < sellAmount) {
uint256 amountToWrap = sellAmount - availableNativeToken;
require(address(this).balance >= amountToWrap, "not enough ETH to wrap");
if (address(this).balance < amountToWrap) {
amountToWrap = address(this).balance;
}
// Simulate wrapping the missing `ETH` so the user doesn't have to spend gas
// on that just to get a quote. If they are happy with the quote and want to
// create an order they will actually have to do the wrapping, though.
// create an order they will actually have to do the wrapping, though. We
// only wrap up until the available balance to allow for balance overrides
// to fake the remaining amount.
INativeERC20(nativeToken).deposit{value: amountToWrap}();
}
}
Expand Down
61 changes: 54 additions & 7 deletions crates/e2e/tests/e2e/quote_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ use {
number::nonzero::U256 as NonZeroU256,
shared::{
price_estimation::{
trade_verifier::{PriceQuery, TradeVerifier, TradeVerifying},
trade_verifier::{
balance_overrides::BalanceOverrides,
PriceQuery,
TradeVerifier,
TradeVerifying,
},
Estimate,
Verification,
},
Expand Down Expand Up @@ -109,6 +114,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) {
web3.clone(),
Arc::new(web3.clone()),
Arc::new(web3.clone()),
Arc::new(BalanceOverrides::default()),
block_stream,
onchain.contracts().gp_settlement.address(),
onchain.contracts().weth.address(),
Expand Down Expand Up @@ -268,30 +274,71 @@ async fn verified_quote_with_simulated_balance(web3: Web3) {
let [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
.await;
let weth = &onchain.contracts().weth;

tracing::info!("Starting services.");
let services = Services::new(&onchain).await;
services
.start_protocol_with_args(
ExtraServiceArgs {
api: vec![format!(
api: vec![
// The OpenZeppelin `ERC20Mintable` token uses a mapping in
// the first (0'th) storage slot for balances.
"--quote-token-balance-overrides={:?}@0",
token.address()
)],
format!("--quote-token-balance-overrides={:?}@0", token.address()),
// We don't configure the WETH token and instead rely on
// auto-detection for balance overrides.
"--quote-autodetect-token-balance-overrides=true".to_string(),
],
..Default::default()
},
solver,
)
.await;

// quote where the trader has no balances or approval set.
// quote where the trader has no balances or approval set from TOKEN->WETH
assert_eq!(
(
token.balance_of(trader.address()).call().await.unwrap(),
token
.allowance(trader.address(), onchain.contracts().allowance)
.call()
.await
.unwrap(),
),
(U256::zero(), U256::zero()),
);
let response = services
.submit_quote(&OrderQuoteRequest {
from: trader.address(),
sell_token: token.address(),
buy_token: onchain.contracts().weth.address(),
buy_token: weth.address(),
side: OrderQuoteSide::Sell {
sell_amount: SellAmount::BeforeFee {
value: to_wei(1).try_into().unwrap(),
},
},
..Default::default()
})
.await
.unwrap();
assert!(response.verified);

// quote where the trader has no balances or approval set from WETH->TOKEN
assert_eq!(
(
weth.balance_of(trader.address()).call().await.unwrap(),
weth.allowance(trader.address(), onchain.contracts().allowance)
.call()
.await
.unwrap(),
),
(U256::zero(), U256::zero()),
);
let response = services
.submit_quote(&OrderQuoteRequest {
from: trader.address(),
sell_token: weth.address(),
buy_token: token.address(),
side: OrderQuoteSide::Sell {
sell_amount: SellAmount::BeforeFee {
value: to_wei(1).try_into().unwrap(),
Expand Down
15 changes: 5 additions & 10 deletions crates/shared/src/price_estimation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
self::trade_verifier::balance_overrides::ConfigurationBalanceOverrides,
self::trade_verifier::balance_overrides,
crate::{
arguments::{display_option, display_secret_option, ExternalSolver},
conversions::U256Ext,
Expand Down Expand Up @@ -214,9 +214,8 @@ pub struct Arguments {
)]
pub quote_timeout: Duration,

/// Token configuration for simulated balances on verified quotes.
#[clap(long, env, default_value_t)]
pub quote_token_balance_overrides: ConfigurationBalanceOverrides,
#[clap(flatten)]
pub balance_overrides: balance_overrides::Arguments,
}

#[derive(clap::Parser)]
Expand Down Expand Up @@ -296,7 +295,7 @@ impl Display for Arguments {
quote_inaccuracy_limit,
quote_verification,
quote_timeout,
quote_token_balance_overrides,
balance_overrides,
} = self;

display_option(
Expand Down Expand Up @@ -374,11 +373,7 @@ impl Display for Arguments {
writeln!(f, "quote_inaccuracy_limit: {}", quote_inaccuracy_limit)?;
writeln!(f, "quote_verification: {:?}", quote_verification)?;
writeln!(f, "quote_timeout: {:?}", quote_timeout)?;
writeln!(
f,
"quote_token_balance_overrides: {:?}",
quote_token_balance_overrides
)?;
write!(f, "{}", balance_overrides)?;

Ok(())
}
Expand Down
5 changes: 3 additions & 2 deletions crates/shared/src/price_estimation/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,20 @@ impl<'a> PriceEstimatorFactory<'a> {
None => Arc::new(web3.clone()),
};

let balance_overrides = Arc::new(args.quote_token_balance_overrides.clone());
let balance_overrides = args.balance_overrides.init(simulator.clone());

let verifier = TradeVerifier::new(
web3,
simulator,
components.code_fetcher.clone(),
balance_overrides,
network.block_stream.clone(),
network.settlement,
network.native_token,
args.quote_inaccuracy_limit,
);

Some(Arc::new(verifier.with_balance_overrides(balance_overrides)))
Some(Arc::new(verifier))
}

fn native_token_price_estimation_amount(&self) -> Result<NonZeroU256> {
Expand Down
36 changes: 15 additions & 21 deletions crates/shared/src/price_estimation/trade_verifier.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
pub mod balance_overrides;

use {
self::balance_overrides::{
BalanceOverrideRequest,
BalanceOverriding,
ConfigurationBalanceOverrides,
},
self::balance_overrides::{BalanceOverrideRequest, BalanceOverriding},
super::{Estimate, Verification},
crate::{
code_fetching::CodeFetching,
Expand Down Expand Up @@ -65,10 +61,12 @@ impl TradeVerifier {
const SPARDOSE: H160 = addr!("0000000000000000000000000000000000020000");
const TRADER_IMPL: H160 = addr!("0000000000000000000000000000000000010000");

#[allow(clippy::too_many_arguments)]
pub fn new(
web3: Web3,
simulator: Arc<dyn CodeSimulating>,
code_fetcher: Arc<dyn CodeFetching>,
balance_overrides: Arc<dyn BalanceOverriding>,
block_stream: CurrentBlockWatcher,
settlement: H160,
native_token: H160,
Expand All @@ -77,7 +75,7 @@ impl TradeVerifier {
Self {
simulator,
code_fetcher,
balance_overrides: Arc::new(ConfigurationBalanceOverrides::default()),
balance_overrides,
block_stream,
settlement: GPv2Settlement::at(&web3, settlement),
native_token,
Expand All @@ -87,11 +85,6 @@ impl TradeVerifier {
}
}

pub fn with_balance_overrides(mut self, balance_overrides: Arc<dyn BalanceOverriding>) -> Self {
self.balance_overrides = balance_overrides;
self
}

async fn verify_inner(
&self,
query: &PriceQuery,
Expand Down Expand Up @@ -302,16 +295,17 @@ impl TradeVerifier {
// safe to mock the trade pre-conditions on behalf of the user. We use
// a similar strategy for determining whether or not to set approvals on
// behalf of the trader.
if let Some(solver_balance_override) =
self.balance_overrides
.state_override(&BalanceOverrideRequest {
token: query.sell_token,
holder: Self::SPARDOSE,
amount: match query.kind {
OrderKind::Sell => query.in_amount.get(),
OrderKind::Buy => trade.out_amount,
},
})
if let Some(solver_balance_override) = self
.balance_overrides
.state_override(BalanceOverrideRequest {
token: query.sell_token,
holder: Self::SPARDOSE,
amount: match query.kind {
OrderKind::Sell => query.in_amount.get(),
OrderKind::Buy => trade.out_amount,
},
})
.await
{
tracing::debug!(?solver_balance_override, "solver balance override enabled");
overrides.insert(query.sell_token, solver_balance_override);
Expand Down
Loading

0 comments on commit 2be21cf

Please sign in to comment.