Skip to main content

tycho_simulation/evm/protocol/vm/
adapter_contract.rs

1use std::{
2    collections::{HashMap, HashSet},
3    fmt::Debug,
4};
5
6use alloy::{
7    primitives::{Address, U256},
8    sol_types::SolValue,
9};
10use revm::DatabaseRef;
11use tycho_common::simulation::errors::SimulationError;
12
13use super::{
14    erc20_token::Overwrites, models::Capability, tycho_simulation_contract::TychoSimulationContract,
15};
16use crate::evm::{
17    account_storage::StateUpdate,
18    engine_db::engine_db_interface::EngineDatabaseInterface,
19    protocol::{u256_num::u256_to_f64, vm::utils::string_to_bytes32},
20    simulation::BlockEnvOverrides,
21};
22
23#[derive(Debug)]
24pub struct Trade {
25    pub received_amount: U256,
26    pub gas_used: U256,
27    pub price: f64,
28}
29
30/// Type aliases are defined to ensure compatibility with `alloy::sol_types::abi_decode`,
31/// which requires explicit types matching the Solidity ABI. These aliases correspond
32/// directly to the outputs of the contract's functions.
33/// These types ensure correct decoding and alignment with the ABI.
34type PriceReturn = Vec<(U256, U256)>;
35type SwapReturn = (U256, U256, (U256, U256));
36type LimitsReturn = Vec<U256>;
37type CapabilitiesReturn = Vec<U256>;
38type MinGasUsageReturn = U256;
39
40/// An implementation of `TychoSimulationContract` specific to the `AdapterContract` ABI interface,
41/// providing methods for price calculations, token swaps, capability checks, and more.
42///
43/// This struct facilitates interaction with the `AdapterContract` by encoding and decoding data
44/// according to its ABI specification. Each method corresponds to a function in the adapter
45/// contract's interface, enabling seamless integration with tycho's simulation environment.
46///
47/// # Methods
48/// - `price`: Calculates price information for a token pair within the adapter.
49/// - `swap`: Simulates a token swap operation, returning details about the trade and state updates.
50/// - `get_limits`: Retrieves the trade limits for a given token pair.
51/// - `get_capabilities`: Checks the capabilities of the adapter for a specific token pair.
52/// - `min_gas_usage`: Queries the minimum gas usage required for operations within the adapter.
53impl<D: EngineDatabaseInterface + Clone + Debug> TychoSimulationContract<D>
54where
55    <D as DatabaseRef>::Error: Debug,
56    <D as EngineDatabaseInterface>::Error: Debug,
57{
58    pub fn price(
59        &self,
60        pair_id: &str,
61        sell_token: Address,
62        buy_token: Address,
63        amounts: Vec<U256>,
64        overwrites: Option<HashMap<Address, Overwrites>>,
65        block_overrides: Option<BlockEnvOverrides>,
66    ) -> Result<Vec<f64>, SimulationError> {
67        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token, amounts);
68        let selector = "price(bytes32,address,address,uint256[])";
69
70        let res = self
71            .call(selector, args, overwrites, None, U256::from(0u64), None, block_overrides)?
72            .return_value;
73
74        let decoded: PriceReturn = PriceReturn::abi_decode(&res).map_err(|e| {
75            SimulationError::FatalError(format!("Failed to decode price return value: {e:?}"))
76        })?;
77
78        let price = self.calculate_price(decoded)?;
79        Ok(price)
80    }
81
82    #[allow(clippy::too_many_arguments)]
83    pub fn swap(
84        &self,
85        pair_id: &str,
86        sell_token: Address,
87        buy_token: Address,
88        is_buy: bool,
89        amount: U256,
90        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
91        block_overrides: Option<BlockEnvOverrides>,
92    ) -> Result<(Trade, HashMap<Address, StateUpdate>), SimulationError> {
93        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token, is_buy, amount);
94        let selector = "swap(bytes32,address,address,uint8,uint256)";
95
96        let res =
97            self.call(selector, args, overwrites, None, U256::from(0u64), None, block_overrides)?;
98
99        let decoded: SwapReturn = SwapReturn::abi_decode(&res.return_value).map_err(|_| {
100            SimulationError::FatalError(format!(
101                "Adapter swap call failed: Failed to decode return value. Expected amount, gas, and price elements in the format (U256, U256, (U256, U256)). Found {:?}",
102                        &res.return_value[..],
103            ))
104        })?;
105
106        let (received_amount, gas_used, price_elements) = decoded;
107
108        let price = self
109            .calculate_price(vec![price_elements])?
110            .first()
111            .cloned()
112            .ok_or_else(|| {
113                SimulationError::FatalError(
114                    "Adapter swap call failed: An empty price list was returned".into(),
115                )
116            })?;
117
118        Ok((Trade { received_amount, gas_used, price }, res.simulation_result.state_updates))
119    }
120
121    pub fn get_limits(
122        &self,
123        pair_id: &str,
124        sell_token: Address,
125        buy_token: Address,
126        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
127        block_overrides: Option<BlockEnvOverrides>,
128    ) -> Result<(U256, U256), SimulationError> {
129        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token);
130        let selector = "getLimits(bytes32,address,address)";
131
132        let res = self
133            .call(selector, args, overwrites, None, U256::from(0u64), None, block_overrides)?
134            .return_value;
135
136        let decoded: LimitsReturn = LimitsReturn::abi_decode(&res).map_err(|e| {
137            SimulationError::FatalError(format!(
138                "Adapter get_limits call failed: Failed to decode return value: {e:?}"
139            ))
140        })?;
141
142        Ok((decoded[0], decoded[1]))
143    }
144
145    pub fn get_capabilities(
146        &self,
147        pair_id: &str,
148        sell_token: Address,
149        buy_token: Address,
150    ) -> Result<HashSet<Capability>, SimulationError> {
151        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token);
152        let selector = "getCapabilities(bytes32,address,address)";
153
154        let res = self
155            .call(selector, args, None, None, U256::from(0u64), None, None)?
156            .return_value;
157        let decoded: CapabilitiesReturn = CapabilitiesReturn::abi_decode(&res).map_err(|e| {
158            SimulationError::FatalError(format!(
159                "Adapter get_capabilities call failed: Failed to decode return value: {e:?}"
160            ))
161        })?;
162
163        let capabilities: HashSet<Capability> = decoded
164            .into_iter()
165            .filter_map(|value| Capability::from_u256(value).ok())
166            .collect();
167
168        Ok(capabilities)
169    }
170
171    #[allow(dead_code)]
172    pub fn min_gas_usage(&self) -> Result<u64, SimulationError> {
173        let args = ();
174        let selector = "minGasUsage()";
175
176        let res = self
177            .call(selector, args, None, None, U256::from(0u64), None, None)?
178            .return_value;
179
180        let decoded: MinGasUsageReturn = MinGasUsageReturn::abi_decode(&res).map_err(|e| {
181            SimulationError::FatalError(format!(
182                "Adapter min gas usage call failed: Failed to decode return value: {e:?}"
183            ))
184        })?;
185        decoded
186            .try_into()
187            .map_err(|_| SimulationError::FatalError("Decoded value exceeds u64 range".to_string()))
188    }
189
190    fn calculate_price(&self, fractions: Vec<(U256, U256)>) -> Result<Vec<f64>, SimulationError> {
191        fractions
192            .into_iter()
193            .map(|(numerator, denominator)| {
194                if denominator.is_zero() {
195                    Err(SimulationError::FatalError(
196                        "Adapter price calculation failed: Denominator is zero".to_string(),
197                    ))
198                } else {
199                    let num_f64 = u256_to_f64(numerator)?;
200                    let den_f64 = u256_to_f64(denominator)?;
201                    Ok(num_f64 / den_f64)
202                }
203            })
204            .collect()
205    }
206}