Skip to content

Commit

Permalink
Treat ETH and WETH as single token in winners selection (#3074)
Browse files Browse the repository at this point in the history
# Description
Fixes #3043

Treats ETH and WETH as a same token in winner detection.
  • Loading branch information
sunce86 authored Oct 22, 2024
1 parent 079db75 commit 388486d
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 10 deletions.
30 changes: 30 additions & 0 deletions crates/autopilot/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ use {
derive_more::{Display, From, Into},
};

/// ERC20 token address for ETH. In reality, ETH is not an ERC20 token because
/// it does not implement the ERC20 interface, but this address is used by
/// convention across the Ethereum ecosystem whenever ETH is treated like an
/// ERC20 token.
/// Same address is also used for XDAI on Gnosis Chain.
pub const NATIVE_TOKEN: TokenAddress = TokenAddress(H160([0xee; 20]));

/// An address. Can be an EOA or a smart contract address.
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display,
Expand Down Expand Up @@ -33,6 +40,29 @@ pub struct TxId(pub H256);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
pub struct TokenAddress(pub H160);

impl TokenAddress {
/// If the token is ETH/XDAI, return WETH/WXDAI, thereby converting it to
/// erc20.
pub fn as_erc20(self, wrapped: WrappedNativeToken) -> Self {
if self == NATIVE_TOKEN {
wrapped.into()
} else {
self
}
}
}

/// ERC20 representation of the chain's native token (e.g. WETH on mainnet,
/// WXDAI on Gnosis Chain).
#[derive(Debug, Clone, Copy, From, Into)]
pub struct WrappedNativeToken(TokenAddress);

impl From<H160> for WrappedNativeToken {
fn from(value: H160) -> Self {
WrappedNativeToken(value.into())
}
}

/// An ERC20 token amount.
///
/// https://eips.ethereum.org/EIPS/eip-20
Expand Down
6 changes: 6 additions & 0 deletions crates/autopilot/src/infra/blockchain/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ impl Contracts {
&self.weth
}

/// Wrapped version of the native token (e.g. WETH for Ethereum, WXDAI for
/// Gnosis Chain)
pub fn wrapped_native_token(&self) -> domain::eth::WrappedNativeToken {
self.weth.address().into()
}

pub fn authenticator(&self) -> &contracts::GPv2AllowListAuthentication {
&self.authenticator
}
Expand Down
8 changes: 7 additions & 1 deletion crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ impl RunLoop {
// until `max_winners_per_auction` are selected. The solution is a winner
// if it swaps tokens that are not yet swapped by any previously processed
// solution.
let wrapped_native_token = self.eth.contracts().wrapped_native_token();
let mut already_swapped_tokens = HashSet::new();
let mut winners = 0;
let solutions = solutions
Expand All @@ -579,7 +580,12 @@ impl RunLoop {
.solution()
.orders()
.iter()
.flat_map(|(_, order)| [order.sell.token, order.buy.token])
.flat_map(|(_, order)| {
[
order.sell.token.as_erc20(wrapped_native_token),
order.buy.token.as_erc20(wrapped_native_token),
]
})
.collect::<HashSet<_>>();

let is_winner = swapped_tokens.is_disjoint(&already_swapped_tokens)
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/competition/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl Auction {
// Ensure that tokens are included for each order.
let weth = eth.contracts().weth_address();
if !orders.iter().all(|order| {
tokens.0.contains_key(&order.buy.token.wrap(weth))
tokens.0.contains_key(&order.buy.token.as_erc20(weth))
&& tokens.0.contains_key(&order.sell.token)
}) {
return Err(Error::InvalidTokens);
Expand Down
8 changes: 5 additions & 3 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ impl Solution {
match &trade {
Trade::Fulfillment(fulfillment) => {
let prices = ClearingPrices {
sell: solution.prices[&fulfillment.order().sell.token.wrap(solution.weth)],
buy: solution.prices[&fulfillment.order().buy.token.wrap(solution.weth)],
sell: solution.prices
[&fulfillment.order().sell.token.as_erc20(solution.weth)],
buy: solution.prices
[&fulfillment.order().buy.token.as_erc20(solution.weth)],
};
let fulfillment = fulfillment.with_protocol_fees(prices)?;
trades.push(Trade::Fulfillment(fulfillment))
Expand Down Expand Up @@ -443,7 +445,7 @@ impl Solution {
/// Clearing price for the given token.
pub fn clearing_price(&self, token: eth::TokenAddress) -> Option<eth::U256> {
// The clearing price of ETH is equal to WETH.
let token = token.wrap(self.weth);
let token = token.as_erc20(self.weth);
self.prices.get(&token).map(ToOwned::to_owned)
}

Expand Down
5 changes: 3 additions & 2 deletions crates/driver/src/domain/competition/solution/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,9 @@ impl Settlement {
let order = match trade {
Trade::Fulfillment(_) => {
let prices = ClearingPrices {
sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)],
buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)],
sell: self.solution.prices
[&trade.sell().token.as_erc20(self.solution.weth)],
buy: self.solution.prices[&trade.buy().token.as_erc20(self.solution.weth)],
};
competition::Amounts {
side: trade.side(),
Expand Down
4 changes: 2 additions & 2 deletions crates/driver/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ impl From<ContractAddress> for Address {
pub struct TokenAddress(pub ContractAddress);

impl TokenAddress {
/// If the token is ETH, return WETH, thereby "wrapping" it.
pub fn wrap(self, weth: WethAddress) -> Self {
/// If the token is ETH, return WETH, thereby converting it to erc20.
pub fn as_erc20(self, weth: WethAddress) -> Self {
if self == ETH_TOKEN {
weth.into()
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/infra/solver/dto/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl Auction {
let mut available = order.available();

if solver_native_token.wrap_address {
available.buy.token = available.buy.token.wrap(weth)
available.buy.token = available.buy.token.as_erc20(weth)
}
// In case of volume based fees, fee withheld by driver might be higher than the
// surplus of the solution. This would lead to violating limit prices when
Expand Down

0 comments on commit 388486d

Please sign in to comment.