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}