tycho_execution/encoding/
models.rs

1use clap::ValueEnum;
2use num_bigint::BigUint;
3use serde::{Deserialize, Serialize};
4use tycho_common::{
5    models::protocol::ProtocolComponent, simulation::protocol_sim::ProtocolSim, Bytes,
6};
7
8use crate::encoding::serde_primitives::biguint_string;
9
10/// Specifies the method for transferring user funds into Tycho execution.
11///
12/// Options:
13///
14/// - `TransferFromPermit2`: Use Permit2 for token transfer.
15///     - You must manually approve the Permit2 contract and sign the permit object externally
16///       (outside `tycho-execution`).
17///
18/// - `TransferFrom`: Use standard ERC-20 approval and `transferFrom`.
19///     - You must approve the Tycho Router contract to spend your tokens via standard `approve()`
20///       calls.
21///
22/// - `None`: No transfer will be performed.
23///     - Assumes the tokens are already present in the Tycho Router.
24///     - **Warning**: This is an advanced mode. Ensure your logic guarantees that the tokens are
25///       already in the router at the time of execution.
26///     - The Tycho router is **not** designed to safely hold tokens. If tokens are not transferred
27///       and used in the **same transaction**, they will be permanently lost.
28#[derive(Clone, Debug, PartialEq, ValueEnum)]
29pub enum UserTransferType {
30    TransferFromPermit2,
31    TransferFrom,
32    None,
33}
34
35/// Represents a solution containing details describing an order, and  instructions for filling
36/// the order.
37#[derive(Clone, Default, Debug, Deserialize, Serialize)]
38pub struct Solution<'a> {
39    /// Address of the sender.
40    pub sender: Bytes,
41    /// Address of the receiver.
42    pub receiver: Bytes,
43    /// The token being sold (exact in) or bought (exact out).
44    pub given_token: Bytes,
45    /// Amount of the given token.
46    #[serde(with = "biguint_string")]
47    pub given_amount: BigUint,
48    /// The token being bought (exact in) or sold (exact out).
49    pub checked_token: Bytes,
50    /// False if the solution is an exact input solution. Currently only exact input solutions are
51    /// supported.
52    #[serde(default)]
53    pub exact_out: bool,
54    /// Minimum amount to be checked for the solution to be valid.
55    #[serde(with = "biguint_string")]
56    pub checked_amount: BigUint,
57    /// List of swaps to fulfill the solution.
58    pub swaps: Vec<Swap<'a>>,
59    /// If set, the corresponding native action will be executed.
60    pub native_action: Option<NativeAction>,
61}
62
63/// Represents an action to be performed on the native token either before or after the swap.
64///
65/// `Wrap` means that the native token will be wrapped before the first swap, and `Unwrap`
66/// means that the native token will be unwrapped after the last swap, before being sent to the
67/// receiver.
68#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
69#[serde(rename_all = "snake_case")]
70pub enum NativeAction {
71    Wrap,
72    Unwrap,
73}
74
75/// Represents a swap operation to be performed on a pool.
76#[derive(Clone, Debug, Deserialize, Serialize)]
77pub struct Swap<'a> {
78    /// Protocol component from tycho indexer
79    pub component: ProtocolComponent,
80    /// Token being input into the pool.
81    pub token_in: Bytes,
82    /// Token being output from the pool.
83    pub token_out: Bytes,
84    /// Decimal of the amount to be swapped in this operation (for example, 0.5 means 50%)
85    #[serde(default)]
86    pub split: f64,
87    /// Optional user data to be passed to encoding.
88    pub user_data: Option<Bytes>,
89    /// Optional protocol state used to perform the swap.
90    #[serde(skip)]
91    pub protocol_state: Option<&'a dyn ProtocolSim>,
92}
93
94impl<'a> Swap<'a> {
95    pub fn new<T: Into<ProtocolComponent>>(
96        component: T,
97        token_in: Bytes,
98        token_out: Bytes,
99        split: f64,
100        user_data: Option<Bytes>,
101        protocol_state: Option<&'a dyn ProtocolSim>,
102    ) -> Self {
103        Self { component: component.into(), token_in, token_out, split, user_data, protocol_state }
104    }
105}
106
107impl<'a> PartialEq for Swap<'a> {
108    fn eq(&self, other: &Self) -> bool {
109        self.component == other.component &&
110            self.token_in == other.token_in &&
111            self.token_out == other.token_out &&
112            self.split == other.split &&
113            self.user_data == other.user_data
114        // Skip protocol_state comparison since trait objects don't implement PartialEq
115    }
116}
117
118/// Represents a transaction to be executed.
119///
120/// # Fields
121/// * `to`: Address of the contract to call with the calldata
122/// * `value`: Native token value to be sent with the transaction.
123/// * `data`: Encoded calldata for the transaction.
124#[derive(Clone, Debug)]
125pub struct Transaction {
126    pub to: Bytes,
127    pub value: BigUint,
128    pub data: Vec<u8>,
129}
130
131/// Represents a solution that has been encoded for execution.
132///
133/// # Fields
134/// * `swaps`: Encoded swaps to be executed.
135/// * `interacting_with`: Address of the contract to be called.
136/// * `function_signature`: The signature of the function to be called.
137/// * `n_tokens`: Number of tokens in the swap.
138/// * `permit`: Optional permit for the swap (if permit2 is enabled).
139#[derive(Clone, Debug)]
140pub struct EncodedSolution {
141    pub swaps: Vec<u8>,
142    pub interacting_with: Bytes,
143    pub function_signature: String,
144    pub n_tokens: usize,
145    pub permit: Option<PermitSingle>,
146}
147
148/// Represents a single permit for permit2.
149///
150/// # Fields
151/// * `details`: The details of the permit, such as token, amount, expiration, and nonce.
152/// * `spender`: The address authorized to spend the tokens.
153/// * `sig_deadline`: The deadline (as a timestamp) for the permit signature
154#[derive(Debug, Clone)]
155pub struct PermitSingle {
156    pub details: PermitDetails,
157    pub spender: Bytes,
158    pub sig_deadline: BigUint,
159}
160
161/// Details of a permit.
162///
163/// # Fields
164/// * `token`: The token address for which the permit is granted.
165/// * `amount`: The amount of tokens approved for spending.
166/// * `expiration`: The expiration time (as a timestamp) for the permit.
167/// * `nonce`: The unique nonce to prevent replay attacks.
168#[derive(Debug, Clone)]
169pub struct PermitDetails {
170    pub token: Bytes,
171    pub amount: BigUint,
172    pub expiration: BigUint,
173    pub nonce: BigUint,
174}
175
176impl PartialEq for PermitSingle {
177    fn eq(&self, other: &Self) -> bool {
178        self.details == other.details && self.spender == other.spender
179        // sig_deadline is intentionally ignored
180    }
181}
182
183impl PartialEq for PermitDetails {
184    fn eq(&self, other: &Self) -> bool {
185        self.token == other.token && self.amount == other.amount && self.nonce == other.nonce
186        // expiration is intentionally ignored
187    }
188}
189
190/// Represents necessary attributes for encoding an order.
191///
192/// # Fields
193///
194/// * `receiver`: Address of the receiver of the out token after the swaps are completed.
195/// * `exact_out`: true if the solution is a buy order, false if it is a sell order.
196/// * `router_address`: Address of the router contract to be used for the swaps. Zero address if
197///   solution does not require router address.
198/// * `group_token_in`: Token to be used as the input for the group swap.
199/// * `group_token_out`: Token to be used as the output for the group swap.
200/// * `transfer`: Type of transfer to be performed. See `TransferType` for more details.
201#[derive(Clone, Debug)]
202pub struct EncodingContext {
203    pub receiver: Bytes,
204    pub exact_out: bool,
205    pub router_address: Option<Bytes>,
206    pub group_token_in: Bytes,
207    pub group_token_out: Bytes,
208    pub transfer_type: TransferType,
209}
210
211/// Represents the type of transfer to be performed into the pool.
212///
213/// # Fields
214///
215/// * `TransferFrom`: Transfer the token from the sender to the protocol/router.
216/// * `Transfer`: Transfer the token from the router into the protocol.
217/// * `None`: No transfer is needed. Tokens are already in the pool.
218#[repr(u8)]
219#[derive(Copy, Clone, Debug, PartialEq)]
220pub enum TransferType {
221    TransferFrom = 0,
222    Transfer = 1,
223    None = 2,
224}
225
226mod tests {
227    use super::*;
228
229    struct MockProtocolComponent {
230        id: String,
231        protocol_system: String,
232    }
233
234    impl From<MockProtocolComponent> for ProtocolComponent {
235        fn from(component: MockProtocolComponent) -> Self {
236            ProtocolComponent {
237                id: component.id,
238                protocol_system: component.protocol_system,
239                tokens: vec![],
240                protocol_type_name: "".to_string(),
241                chain: Default::default(),
242                contract_addresses: vec![],
243                static_attributes: Default::default(),
244                change: Default::default(),
245                creation_tx: Default::default(),
246                created_at: Default::default(),
247            }
248        }
249    }
250
251    #[test]
252    fn test_swap_new() {
253        let component = MockProtocolComponent {
254            id: "i-am-an-id".to_string(),
255            protocol_system: "uniswap_v2".to_string(),
256        };
257        let user_data = Some(Bytes::from("0x1234"));
258        let swap = Swap::new(
259            component,
260            Bytes::from("0x12"),
261            Bytes::from("34"),
262            0.5,
263            user_data.clone(),
264            None,
265        );
266        assert_eq!(swap.token_in, Bytes::from("0x12"));
267        assert_eq!(swap.token_out, Bytes::from("0x34"));
268        assert_eq!(swap.component.protocol_system, "uniswap_v2");
269        assert_eq!(swap.component.id, "i-am-an-id");
270        assert_eq!(swap.split, 0.5);
271        assert_eq!(swap.user_data, user_data);
272    }
273}