tempo_precompiles/tip_fee_manager/
dispatch.rs1use crate::{
4 Precompile, charge_input_cost, dispatch_call, metadata, mutate, mutate_void,
5 storage::Handler,
6 tip_fee_manager::{
7 ITIPFeeAMM, TipFeeManager,
8 amm::{M, MIN_LIQUIDITY, N, SCALE},
9 },
10 view,
11};
12use alloy::{primitives::Address, sol_types::SolInterface};
13use revm::precompile::PrecompileResult;
14use tempo_contracts::precompiles::{IFeeManager::IFeeManagerCalls, ITIPFeeAMM::ITIPFeeAMMCalls};
15
16enum TipFeeManagerCall {
18 FeeManager(IFeeManagerCalls),
19 Amm(ITIPFeeAMMCalls),
20}
21
22impl TipFeeManagerCall {
23 fn decode(calldata: &[u8]) -> Result<Self, alloy::sol_types::Error> {
24 let selector: [u8; 4] = calldata[..4].try_into().expect("calldata len >= 4");
26
27 if IFeeManagerCalls::valid_selector(selector) {
28 IFeeManagerCalls::abi_decode(calldata).map(Self::FeeManager)
29 } else {
30 ITIPFeeAMMCalls::abi_decode(calldata).map(Self::Amm)
31 }
32 }
33}
34
35impl Precompile for TipFeeManager {
36 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
37 if let Some(err) = charge_input_cost(&mut self.storage, calldata) {
38 return err;
39 }
40
41 dispatch_call(
42 calldata,
43 &[],
44 TipFeeManagerCall::decode,
45 |call| match call {
46 TipFeeManagerCall::FeeManager(IFeeManagerCalls::userTokens(call)) => {
48 view(call, |c| self.user_tokens(c))
49 }
50 TipFeeManagerCall::FeeManager(IFeeManagerCalls::validatorTokens(call)) => {
51 view(call, |c| self.get_validator_token(c.validator))
52 }
53 TipFeeManagerCall::FeeManager(IFeeManagerCalls::collectedFees(call)) => {
54 view(call, |c| self.collected_fees[c.validator][c.token].read())
55 }
56
57 TipFeeManagerCall::FeeManager(IFeeManagerCalls::setValidatorToken(call)) => {
59 mutate_void(call, msg_sender, |s, c| {
60 let beneficiary = self.storage.beneficiary();
61 self.set_validator_token(s, c, beneficiary)
62 })
63 }
64 TipFeeManagerCall::FeeManager(IFeeManagerCalls::setUserToken(call)) => {
65 mutate_void(call, msg_sender, |s, c| self.set_user_token(s, c))
66 }
67 TipFeeManagerCall::FeeManager(IFeeManagerCalls::distributeFees(call)) => {
68 mutate_void(call, msg_sender, |_, c| {
69 self.distribute_fees(c.validator, c.token)
70 })
71 }
72
73 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::M(_)) => {
75 metadata::<ITIPFeeAMM::MCall>(|| Ok(M))
76 }
77 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::N(_)) => {
78 metadata::<ITIPFeeAMM::NCall>(|| Ok(N))
79 }
80 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::SCALE(_)) => {
81 metadata::<ITIPFeeAMM::SCALECall>(|| Ok(SCALE))
82 }
83 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::MIN_LIQUIDITY(_)) => {
84 metadata::<ITIPFeeAMM::MIN_LIQUIDITYCall>(|| Ok(MIN_LIQUIDITY))
85 }
86
87 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::getPoolId(call)) => {
89 view(call, |c| Ok(self.pool_id(c.userToken, c.validatorToken)))
90 }
91 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::getPool(call)) => {
92 view(call, |c| Ok(self.get_pool(c)?.into()))
93 }
94 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::pools(call)) => {
95 view(call, |c| Ok(self.pools[c.poolId].read()?.into()))
96 }
97 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::totalSupply(call)) => {
98 view(call, |c| self.total_supply[c.poolId].read())
99 }
100 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::liquidityBalances(call)) => {
101 view(call, |c| self.liquidity_balances[c.poolId][c.user].read())
102 }
103
104 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::mint(call)) => {
106 mutate(call, msg_sender, |s, c| {
107 self.mint(
108 s,
109 c.userToken,
110 c.validatorToken,
111 c.amountValidatorToken,
112 c.to,
113 )
114 })
115 }
116 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::burn(call)) => {
117 mutate(call, msg_sender, |s, c| {
118 let (amount_user_token, amount_validator_token) =
119 self.burn(s, c.userToken, c.validatorToken, c.liquidity, c.to)?;
120 Ok(ITIPFeeAMM::burnReturn {
121 amountUserToken: amount_user_token,
122 amountValidatorToken: amount_validator_token,
123 })
124 })
125 }
126 TipFeeManagerCall::Amm(ITIPFeeAMMCalls::rebalanceSwap(call)) => {
127 mutate(call, msg_sender, |s, c| {
128 self.rebalance_swap(s, c.userToken, c.validatorToken, c.amountOut, c.to)
129 })
130 }
131 },
132 )
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::{
140 Precompile, expect_precompile_revert,
141 storage::{ContractStorage, StorageCtx, hashmap::HashMapStorageProvider},
142 test_util::{TIP20Setup, assert_full_coverage, check_selector_coverage},
143 tip_fee_manager::{
144 FeeManagerError,
145 amm::{M, MIN_LIQUIDITY, N, PoolKey, SCALE},
146 },
147 };
148 use alloy::{
149 primitives::{Address, B256, U256},
150 sol_types::{SolCall, SolValue},
151 };
152 use tempo_contracts::precompiles::{
153 IFeeManager, IFeeManager::IFeeManagerCalls, ITIPFeeAMM, ITIPFeeAMM::ITIPFeeAMMCalls,
154 };
155
156 #[test]
157 fn test_set_validator_token() -> eyre::Result<()> {
158 let mut storage = HashMapStorageProvider::new(1);
159 let admin = Address::random();
160 let validator = Address::random();
161 StorageCtx::enter(&mut storage, || {
162 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
163 let mut fee_manager = TipFeeManager::new();
164
165 let calldata = IFeeManager::setValidatorTokenCall {
166 token: token.address(),
167 }
168 .abi_encode();
169 let result = fee_manager.call(&calldata, validator)?;
170 assert!(result.status.is_success());
171
172 let calldata = IFeeManager::validatorTokensCall { validator }.abi_encode();
174 let result = fee_manager.call(&calldata, validator)?;
175 assert!(result.status.is_success());
176 let returned_token = Address::abi_decode(&result.bytes)?;
177 assert_eq!(returned_token, token.address());
178
179 Ok(())
180 })
181 }
182
183 #[test]
184 fn test_set_validator_token_zero_address() -> eyre::Result<()> {
185 let mut storage = HashMapStorageProvider::new(1);
186 let validator = Address::random();
187 StorageCtx::enter(&mut storage, || {
188 let mut fee_manager = TipFeeManager::new();
189
190 let calldata = IFeeManager::setValidatorTokenCall {
191 token: Address::ZERO,
192 }
193 .abi_encode();
194 let result = fee_manager.call(&calldata, validator);
195 expect_precompile_revert(&result, FeeManagerError::invalid_token());
196
197 Ok(())
198 })
199 }
200
201 #[test]
202 fn test_set_user_token() -> eyre::Result<()> {
203 let mut storage = HashMapStorageProvider::new(1);
204 let admin = Address::random();
205 let user = Address::random();
206 StorageCtx::enter(&mut storage, || {
207 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
208 let mut fee_manager = TipFeeManager::new();
209
210 let calldata = IFeeManager::setUserTokenCall {
211 token: token.address(),
212 }
213 .abi_encode();
214 let result = fee_manager.call(&calldata, user)?;
215 assert!(result.status.is_success());
216
217 let calldata = IFeeManager::userTokensCall { user }.abi_encode();
219 let result = fee_manager.call(&calldata, user)?;
220 assert!(result.status.is_success());
221 let returned_token = Address::abi_decode(&result.bytes)?;
222 assert_eq!(returned_token, token.address());
223
224 Ok(())
225 })
226 }
227
228 #[test]
229 fn test_set_user_token_zero_address() -> eyre::Result<()> {
230 let mut storage = HashMapStorageProvider::new(1);
231 let user = Address::random();
232 StorageCtx::enter(&mut storage, || {
233 let mut fee_manager = TipFeeManager::new();
234
235 let calldata = IFeeManager::setUserTokenCall {
236 token: Address::ZERO,
237 }
238 .abi_encode();
239 let result = fee_manager.call(&calldata, user);
240 expect_precompile_revert(&result, FeeManagerError::invalid_token());
241
242 Ok(())
243 })
244 }
245
246 #[test]
247 fn test_get_pool_id() -> eyre::Result<()> {
248 let mut storage = HashMapStorageProvider::new(1);
249 let token_a = Address::random();
250 let token_b = Address::random();
251 let sender = Address::random();
252 StorageCtx::enter(&mut storage, || {
253 let mut fee_manager = TipFeeManager::new();
254
255 let calldata = ITIPFeeAMM::getPoolIdCall {
256 userToken: token_a,
257 validatorToken: token_b,
258 }
259 .abi_encode();
260 let result = fee_manager.call(&calldata, sender)?;
261 assert!(result.status.is_success());
262
263 let returned_id = B256::abi_decode(&result.bytes)?;
264 let expected_id = PoolKey::new(token_a, token_b).get_id();
265 assert_eq!(returned_id, expected_id);
266
267 Ok(())
268 })
269 }
270
271 #[test]
272 fn test_tip_fee_amm_pool_operations() -> eyre::Result<()> {
273 let mut storage = HashMapStorageProvider::new(1);
274 let token_a = Address::random();
275 let token_b = Address::random();
276 let sender = Address::random();
277 StorageCtx::enter(&mut storage, || {
278 let mut fee_manager = TipFeeManager::new();
279
280 let get_pool_call = ITIPFeeAMM::getPoolCall {
282 userToken: token_a,
283 validatorToken: token_b,
284 };
285 let calldata = get_pool_call.abi_encode();
286 let result = fee_manager.call(&calldata, sender)?;
287 assert!(result.status.is_success());
288
289 let pool = ITIPFeeAMM::Pool::abi_decode(&result.bytes)?;
291 assert_eq!(pool.reserveUserToken, 0);
292 assert_eq!(pool.reserveValidatorToken, 0);
293
294 Ok(())
295 })
296 }
297
298 #[test]
299 fn test_pool_id_calculation() -> eyre::Result<()> {
300 let mut storage = HashMapStorageProvider::new(1);
301 let token_a = Address::random();
302 let token_b = Address::random();
303 let sender = Address::random();
304 StorageCtx::enter(&mut storage, || {
305 let mut fee_manager = TipFeeManager::new();
306
307 let calldata1 = ITIPFeeAMM::getPoolIdCall {
309 userToken: token_a,
310 validatorToken: token_b,
311 }
312 .abi_encode();
313 let result1 = fee_manager.call(&calldata1, sender)?;
314 let id1 = B256::abi_decode(&result1.bytes)?;
315
316 let calldata2 = ITIPFeeAMM::getPoolIdCall {
318 userToken: token_b,
319 validatorToken: token_a,
320 }
321 .abi_encode();
322 let result2 = fee_manager.call(&calldata2, sender)?;
323 let id2 = B256::abi_decode(&result2.bytes)?;
324
325 assert_ne!(id1, id2);
327
328 Ok(())
329 })
330 }
331
332 #[test]
333 fn test_fee_manager_invalid_token_error() -> eyre::Result<()> {
334 let mut storage = HashMapStorageProvider::new(1);
335 let user = Address::random();
336 let validator = Address::random();
337 StorageCtx::enter(&mut storage, || {
338 let mut fee_manager = TipFeeManager::new();
339
340 let set_validator_call = IFeeManager::setValidatorTokenCall {
342 token: Address::ZERO,
343 };
344 let result = fee_manager.call(&set_validator_call.abi_encode(), validator);
345 expect_precompile_revert(&result, FeeManagerError::invalid_token());
346
347 let set_user_call = IFeeManager::setUserTokenCall {
349 token: Address::ZERO,
350 };
351 let result = fee_manager.call(&set_user_call.abi_encode(), user);
352 expect_precompile_revert(&result, FeeManagerError::invalid_token());
353
354 Ok(())
355 })
356 }
357
358 #[test]
359 fn test_amm_constants() -> eyre::Result<()> {
360 let mut storage = HashMapStorageProvider::new(1);
361 let sender = Address::random();
362 StorageCtx::enter(&mut storage, || {
363 let mut fee_manager = TipFeeManager::new();
364
365 let result =
366 fee_manager.call(&ITIPFeeAMM::MIN_LIQUIDITYCall {}.abi_encode(), sender)?;
367 assert!(!result.is_revert());
368 assert_eq!(U256::abi_decode(&result.bytes)?, MIN_LIQUIDITY);
369
370 let result = fee_manager.call(&ITIPFeeAMM::MCall {}.abi_encode(), sender)?;
371 assert_eq!(U256::abi_decode(&result.bytes)?, M);
372
373 let result = fee_manager.call(&ITIPFeeAMM::NCall {}.abi_encode(), sender)?;
374 assert_eq!(U256::abi_decode(&result.bytes)?, N);
375
376 let result = fee_manager.call(&ITIPFeeAMM::SCALECall {}.abi_encode(), sender)?;
377 assert_eq!(U256::abi_decode(&result.bytes)?, SCALE);
378
379 Ok(())
380 })
381 }
382
383 #[test]
384 fn test_tip_fee_manager_selector_coverage() -> eyre::Result<()> {
385 let mut storage = HashMapStorageProvider::new(1);
386 StorageCtx::enter(&mut storage, || {
387 let mut fee_manager = TipFeeManager::new();
388
389 let fee_manager_unsupported = check_selector_coverage(
390 &mut fee_manager,
391 IFeeManagerCalls::SELECTORS,
392 "IFeeManager",
393 IFeeManagerCalls::name_by_selector,
394 );
395
396 let amm_unsupported = check_selector_coverage(
397 &mut fee_manager,
398 ITIPFeeAMMCalls::SELECTORS,
399 "ITIPFeeAMM",
400 ITIPFeeAMMCalls::name_by_selector,
401 );
402
403 assert_full_coverage([fee_manager_unsupported, amm_unsupported]);
404
405 Ok(())
406 })
407 }
408}