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