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    /// `caller` sets the simulation's `tx.origin`/`msg.sender`; `None` defaults to
59    /// `EXTERNAL_ACCOUNT`.
60    #[allow(clippy::too_many_arguments)]
61    pub fn price(
62        &self,
63        pair_id: &str,
64        sell_token: Address,
65        buy_token: Address,
66        amounts: Vec<U256>,
67        overwrites: Option<HashMap<Address, Overwrites>>,
68        caller: Option<Address>,
69        block_overrides: Option<BlockEnvOverrides>,
70    ) -> Result<Vec<f64>, SimulationError> {
71        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token, amounts);
72        let selector = "price(bytes32,address,address,uint256[])";
73
74        let res = self
75            .call(selector, args, overwrites, caller, U256::from(0u64), None, block_overrides)?
76            .return_value;
77
78        let decoded: PriceReturn = PriceReturn::abi_decode(&res).map_err(|e| {
79            SimulationError::FatalError(format!("Failed to decode price return value: {e:?}"))
80        })?;
81
82        let price = self.calculate_price(decoded)?;
83        Ok(price)
84    }
85
86    #[allow(clippy::too_many_arguments)]
87    pub fn swap(
88        &self,
89        pair_id: &str,
90        sell_token: Address,
91        buy_token: Address,
92        is_buy: bool,
93        amount: U256,
94        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
95        block_overrides: Option<BlockEnvOverrides>,
96    ) -> Result<(Trade, HashMap<Address, StateUpdate>), SimulationError> {
97        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token, is_buy, amount);
98        let selector = "swap(bytes32,address,address,uint8,uint256)";
99
100        let res =
101            self.call(selector, args, overwrites, None, U256::from(0u64), None, block_overrides)?;
102
103        let decoded: SwapReturn = SwapReturn::abi_decode(&res.return_value).map_err(|_| {
104            SimulationError::FatalError(format!(
105                "Adapter swap call failed: Failed to decode return value. Expected amount, gas, and price elements in the format (U256, U256, (U256, U256)). Found {:?}",
106                        &res.return_value[..],
107            ))
108        })?;
109
110        let (received_amount, gas_used, price_elements) = decoded;
111
112        let price = self
113            .calculate_price(vec![price_elements])?
114            .first()
115            .cloned()
116            .ok_or_else(|| {
117                SimulationError::FatalError(
118                    "Adapter swap call failed: An empty price list was returned".into(),
119                )
120            })?;
121
122        Ok((Trade { received_amount, gas_used, price }, res.simulation_result.state_updates))
123    }
124
125    pub fn get_limits(
126        &self,
127        pair_id: &str,
128        sell_token: Address,
129        buy_token: Address,
130        overwrites: Option<HashMap<Address, HashMap<U256, U256>>>,
131        block_overrides: Option<BlockEnvOverrides>,
132    ) -> Result<(U256, U256), SimulationError> {
133        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token);
134        let selector = "getLimits(bytes32,address,address)";
135
136        let res = self
137            .call(selector, args, overwrites, None, U256::from(0u64), None, block_overrides)?
138            .return_value;
139
140        let decoded: LimitsReturn = LimitsReturn::abi_decode(&res).map_err(|e| {
141            SimulationError::FatalError(format!(
142                "Adapter get_limits call failed: Failed to decode return value: {e:?}"
143            ))
144        })?;
145
146        Ok((decoded[0], decoded[1]))
147    }
148
149    pub fn get_capabilities(
150        &self,
151        pair_id: &str,
152        sell_token: Address,
153        buy_token: Address,
154    ) -> Result<HashSet<Capability>, SimulationError> {
155        let args = (string_to_bytes32(pair_id)?, sell_token, buy_token);
156        let selector = "getCapabilities(bytes32,address,address)";
157
158        let res = self
159            .call(selector, args, None, None, U256::from(0u64), None, None)?
160            .return_value;
161        let decoded: CapabilitiesReturn = CapabilitiesReturn::abi_decode(&res).map_err(|e| {
162            SimulationError::FatalError(format!(
163                "Adapter get_capabilities call failed: Failed to decode return value: {e:?}"
164            ))
165        })?;
166
167        let capabilities: HashSet<Capability> = decoded
168            .into_iter()
169            .filter_map(|value| Capability::from_u256(value).ok())
170            .collect();
171
172        Ok(capabilities)
173    }
174
175    #[allow(dead_code)]
176    pub fn min_gas_usage(&self) -> Result<u64, SimulationError> {
177        let args = ();
178        let selector = "minGasUsage()";
179
180        let res = self
181            .call(selector, args, None, None, U256::from(0u64), None, None)?
182            .return_value;
183
184        let decoded: MinGasUsageReturn = MinGasUsageReturn::abi_decode(&res).map_err(|e| {
185            SimulationError::FatalError(format!(
186                "Adapter min gas usage call failed: Failed to decode return value: {e:?}"
187            ))
188        })?;
189        decoded
190            .try_into()
191            .map_err(|_| SimulationError::FatalError("Decoded value exceeds u64 range".to_string()))
192    }
193
194    fn calculate_price(&self, fractions: Vec<(U256, U256)>) -> Result<Vec<f64>, SimulationError> {
195        fractions
196            .into_iter()
197            .map(|(numerator, denominator)| {
198                if denominator.is_zero() {
199                    Err(SimulationError::FatalError(
200                        "Adapter price calculation failed: Denominator is zero".to_string(),
201                    ))
202                } else {
203                    let num_f64 = u256_to_f64(numerator)?;
204                    let den_f64 = u256_to_f64(denominator)?;
205                    Ok(num_f64 / den_f64)
206                }
207            })
208            .collect()
209    }
210}