tycho_simulation/evm/protocol/native_wrapper/
state.rs1use std::{any::Any, collections::HashMap};
2
3use chrono::NaiveDateTime;
4use num_bigint::BigUint;
5use serde::{Deserialize, Serialize};
6use tycho_common::{
7 dto::ProtocolStateDelta,
8 models::{token::Token, Chain},
9 simulation::{
10 errors::{SimulationError, TransitionError},
11 protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
12 },
13 Bytes,
14};
15
16use crate::protocol::models::ProtocolComponent;
17
18pub const NATIVE_WRAPPER_ID: &str = "native_wrapper";
19const NATIVE_WRAPPER_PROTOCOL_SYSTEM: &str = "native_wrapper";
20const NATIVE_WRAPPER_PROTOCOL_TYPE: &str = "NativeWrapper";
21const WRAP_GAS: u64 = 7_000;
22const UNWRAP_GAS: u64 = 14_000;
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct NativeWrapperState {
31 native_token: Token,
32 wrapped_token: Token,
33}
34
35impl NativeWrapperState {
36 pub fn new(chain: Chain) -> Self {
37 Self { native_token: chain.native_token(), wrapped_token: chain.wrapped_native_token() }
38 }
39
40 pub fn component(chain: Chain) -> ProtocolComponent {
42 let native = chain.native_token();
43 let wrapped = chain.wrapped_native_token();
44 ProtocolComponent::new(
45 Bytes::from(NATIVE_WRAPPER_ID.as_bytes()),
46 NATIVE_WRAPPER_PROTOCOL_SYSTEM.to_string(),
47 NATIVE_WRAPPER_PROTOCOL_TYPE.to_string(),
48 chain,
49 vec![native, wrapped],
50 vec![],
51 HashMap::new(),
52 Bytes::default(),
53 NaiveDateTime::default(),
54 )
55 }
56
57 fn validate_tokens(&self, token_in: &Bytes, token_out: &Bytes) -> Result<(), SimulationError> {
58 let valid_pair = (*token_in == self.native_token.address &&
59 *token_out == self.wrapped_token.address) ||
60 (*token_in == self.wrapped_token.address && *token_out == self.native_token.address);
61 if !valid_pair {
62 return Err(SimulationError::InvalidInput(
63 format!(
64 "NativeWrapper only supports {} ↔ {}, got {} → {}",
65 self.native_token.address, self.wrapped_token.address, token_in, token_out,
66 ),
67 None,
68 ));
69 }
70 Ok(())
71 }
72}
73
74#[typetag::serde]
75impl ProtocolSim for NativeWrapperState {
76 fn fee(&self) -> f64 {
77 0.0
78 }
79
80 fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError> {
81 self.validate_tokens(&base.address, "e.address)?;
82 Ok(1.0)
83 }
84
85 fn get_amount_out(
86 &self,
87 amount_in: BigUint,
88 token_in: &Token,
89 token_out: &Token,
90 ) -> Result<GetAmountOutResult, SimulationError> {
91 self.validate_tokens(&token_in.address, &token_out.address)?;
92 let is_wrapping = token_in.address == self.native_token.address;
93 let gas = if is_wrapping { WRAP_GAS } else { UNWRAP_GAS };
94 Ok(GetAmountOutResult::new(amount_in, BigUint::from(gas), self.clone_box()))
95 }
96
97 fn get_limits(
98 &self,
99 sell_token: Bytes,
100 buy_token: Bytes,
101 ) -> Result<(BigUint, BigUint), SimulationError> {
102 self.validate_tokens(&sell_token, &buy_token)?;
103 Ok((BigUint::from(u128::MAX), BigUint::from(u128::MAX)))
104 }
105
106 fn delta_transition(
107 &mut self,
108 _delta: ProtocolStateDelta,
109 _tokens: &HashMap<Bytes, Token>,
110 _balances: &Balances,
111 ) -> Result<(), TransitionError> {
112 Ok(())
113 }
114
115 fn clone_box(&self) -> Box<dyn ProtocolSim> {
116 Box::new(self.clone())
117 }
118
119 fn as_any(&self) -> &dyn Any {
120 self
121 }
122
123 fn as_any_mut(&mut self) -> &mut dyn Any {
124 self
125 }
126
127 fn eq(&self, other: &dyn ProtocolSim) -> bool {
128 other
129 .as_any()
130 .downcast_ref::<NativeWrapperState>()
131 .is_some_and(|o| {
132 self.native_token == o.native_token && self.wrapped_token == o.wrapped_token
133 })
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn eth_state() -> NativeWrapperState {
142 NativeWrapperState::new(Chain::Ethereum)
143 }
144
145 fn native_token() -> Token {
146 Chain::Ethereum.native_token()
147 }
148
149 fn wrapped_token() -> Token {
150 Chain::Ethereum.wrapped_native_token()
151 }
152
153 #[test]
154 fn test_fee_is_zero() {
155 assert_eq!(eth_state().fee(), 0.0);
156 }
157
158 #[test]
159 fn test_spot_price_is_one() {
160 let state = eth_state();
161 let price = state
162 .spot_price(&native_token(), &wrapped_token())
163 .expect("valid pair");
164 assert_eq!(price, 1.0);
165
166 let price = state
167 .spot_price(&wrapped_token(), &native_token())
168 .expect("valid pair");
169 assert_eq!(price, 1.0);
170 }
171
172 #[test]
173 fn test_get_amount_out_wrapping() {
174 let state = eth_state();
175 let amount = BigUint::from(1_000_000u64);
176 let result = state
177 .get_amount_out(amount.clone(), &native_token(), &wrapped_token())
178 .expect("valid pair");
179 assert_eq!(result.amount, amount);
180 assert_eq!(result.gas, BigUint::from(WRAP_GAS));
181 }
182
183 #[test]
184 fn test_get_amount_out_unwrapping() {
185 let state = eth_state();
186 let amount = BigUint::from(1_000_000u64);
187 let result = state
188 .get_amount_out(amount.clone(), &wrapped_token(), &native_token())
189 .expect("valid pair");
190 assert_eq!(result.amount, amount);
191 assert_eq!(result.gas, BigUint::from(UNWRAP_GAS));
192 }
193
194 #[test]
195 fn test_get_amount_out_invalid_pair() {
196 let state = eth_state();
197 let bogus = Token { address: Bytes::from("0xdead"), ..native_token() };
198 let result = state.get_amount_out(BigUint::from(1u64), &bogus, &wrapped_token());
199 assert!(result.is_err());
200 }
201
202 #[test]
203 fn test_get_limits() {
204 let state = eth_state();
205 let (sell_limit, buy_limit) = state
206 .get_limits(native_token().address, wrapped_token().address)
207 .expect("valid pair");
208 assert_eq!(sell_limit, BigUint::from(u128::MAX));
209 assert_eq!(buy_limit, BigUint::from(u128::MAX));
210 }
211
212 #[test]
213 fn test_spot_price_invalid_pair() {
214 let state = eth_state();
215 let bogus = Token { address: Bytes::from("0xdead"), ..native_token() };
216 let result = state.spot_price(&bogus, &wrapped_token());
217 assert!(result.is_err());
218 }
219
220 #[test]
221 fn test_component_metadata() {
222 let component = NativeWrapperState::component(Chain::Ethereum);
223 assert_eq!(component.id, Bytes::from(NATIVE_WRAPPER_ID.as_bytes()));
224 assert_eq!(component.protocol_system, "native_wrapper");
225 assert_eq!(component.protocol_type_name, "NativeWrapper");
226 }
227}