diff --git a/crates/autopilot/src/domain/eth/mod.rs b/crates/autopilot/src/domain/eth/mod.rs index 8ea6975af6..9b187526ba 100644 --- a/crates/autopilot/src/domain/eth/mod.rs +++ b/crates/autopilot/src/domain/eth/mod.rs @@ -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, @@ -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 for WrappedNativeToken { + fn from(value: H160) -> Self { + WrappedNativeToken(value.into()) + } +} + /// An ERC20 token amount. /// /// https://eips.ethereum.org/EIPS/eip-20 diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index 95304db9ad..0db3f67a66 100644 --- a/crates/autopilot/src/infra/blockchain/contracts.rs +++ b/crates/autopilot/src/infra/blockchain/contracts.rs @@ -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 } diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 67428c4f17..e68f185ff3 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -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 @@ -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::>(); let is_winner = swapped_tokens.is_disjoint(&already_swapped_tokens) diff --git a/crates/driver/src/domain/competition/auction.rs b/crates/driver/src/domain/competition/auction.rs index 759add7b01..8764767e6f 100644 --- a/crates/driver/src/domain/competition/auction.rs +++ b/crates/driver/src/domain/competition/auction.rs @@ -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); diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index b8b1a48c38..531568f19f 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -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)) @@ -443,7 +445,7 @@ impl Solution { /// Clearing price for the given token. pub fn clearing_price(&self, token: eth::TokenAddress) -> Option { // 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) } diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index 8fd3111928..c69c582317 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -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(), diff --git a/crates/driver/src/domain/eth/mod.rs b/crates/driver/src/domain/eth/mod.rs index dfea645438..bdf548d32c 100644 --- a/crates/driver/src/domain/eth/mod.rs +++ b/crates/driver/src/domain/eth/mod.rs @@ -129,8 +129,8 @@ impl From 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 { diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index 876a2a2691..8014ee34f7 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -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