1use crate::{
2 Precompile,
3 error::TempoPrecompileError,
4 fill_precompile_output, input_cost, mutate, mutate_void,
5 storage::Handler,
6 tip_fee_manager::{
7 IFeeManager, ITIPFeeAMM, TipFeeManager,
8 amm::{M, MIN_LIQUIDITY, N, SCALE},
9 },
10 unknown_selector, view,
11};
12use alloy::{primitives::Address, sol_types::SolCall};
13use revm::precompile::{PrecompileError, PrecompileResult};
14
15impl Precompile for TipFeeManager {
16 fn call(&mut self, calldata: &[u8], msg_sender: Address) -> PrecompileResult {
17 self.storage
18 .deduct_gas(input_cost(calldata.len()))
19 .map_err(|_| PrecompileError::OutOfGas)?;
20
21 let selector: [u8; 4] = calldata
22 .get(..4)
23 .ok_or_else(|| {
24 PrecompileError::Other("Invalid input: missing function selector".into())
25 })?
26 .try_into()
27 .map_err(|_| PrecompileError::Other("Invalid function selector length".into()))?;
28
29 let result = match selector {
30 IFeeManager::userTokensCall::SELECTOR => {
32 view::<IFeeManager::userTokensCall>(calldata, |call| self.user_tokens(call))
33 }
34 IFeeManager::validatorTokensCall::SELECTOR => {
35 view::<IFeeManager::validatorTokensCall>(calldata, |call| {
36 self.validator_tokens(call)
37 })
38 }
39 IFeeManager::getFeeTokenBalanceCall::SELECTOR => {
40 view::<IFeeManager::getFeeTokenBalanceCall>(calldata, |call| {
41 self.get_fee_token_balance(call)
42 })
43 }
44 ITIPFeeAMM::getPoolIdCall::SELECTOR => {
45 view::<ITIPFeeAMM::getPoolIdCall>(calldata, |call| {
46 Ok(self.pool_id(call.userToken, call.validatorToken))
47 })
48 }
49 ITIPFeeAMM::getPoolCall::SELECTOR => {
50 view::<ITIPFeeAMM::getPoolCall>(calldata, |call| {
51 let pool = self.get_pool(call)?;
52
53 Ok(ITIPFeeAMM::Pool {
54 reserveUserToken: pool.reserve_user_token,
55 reserveValidatorToken: pool.reserve_validator_token,
56 })
57 })
58 }
59 ITIPFeeAMM::poolsCall::SELECTOR => view::<ITIPFeeAMM::poolsCall>(calldata, |call| {
60 let pool = self.pools.at(call.poolId).read()?;
61
62 Ok(ITIPFeeAMM::Pool {
63 reserveUserToken: pool.reserve_user_token,
64 reserveValidatorToken: pool.reserve_validator_token,
65 })
66 }),
67 ITIPFeeAMM::totalSupplyCall::SELECTOR => {
68 view::<ITIPFeeAMM::totalSupplyCall>(calldata, |call| {
69 self.total_supply.at(call.poolId).read()
70 })
71 }
72 ITIPFeeAMM::liquidityBalancesCall::SELECTOR => {
73 view::<ITIPFeeAMM::liquidityBalancesCall>(calldata, |call| {
74 self.liquidity_balances.at(call.poolId).at(call.user).read()
75 })
76 }
77 ITIPFeeAMM::MCall::SELECTOR => view::<ITIPFeeAMM::MCall>(calldata, |_call| Ok(M)),
78 ITIPFeeAMM::NCall::SELECTOR => view::<ITIPFeeAMM::NCall>(calldata, |_call| Ok(N)),
79 ITIPFeeAMM::SCALECall::SELECTOR => {
80 view::<ITIPFeeAMM::SCALECall>(calldata, |_call| Ok(SCALE))
81 }
82 ITIPFeeAMM::MIN_LIQUIDITYCall::SELECTOR => {
83 view::<ITIPFeeAMM::MIN_LIQUIDITYCall>(calldata, |_call| Ok(MIN_LIQUIDITY))
84 }
85
86 IFeeManager::setValidatorTokenCall::SELECTOR => {
88 mutate_void::<IFeeManager::setValidatorTokenCall>(
89 calldata,
90 msg_sender,
91 |s, call| self.set_validator_token(s, call, self.storage.beneficiary()),
92 )
93 }
94 IFeeManager::setUserTokenCall::SELECTOR => {
95 mutate_void::<IFeeManager::setUserTokenCall>(calldata, msg_sender, |s, call| {
96 self.set_user_token(s, call)
97 })
98 }
99 IFeeManager::executeBlockCall::SELECTOR => {
100 mutate_void::<IFeeManager::executeBlockCall>(calldata, msg_sender, |s, _call| {
101 self.execute_block(s, self.storage.beneficiary())
102 })
103 }
104 ITIPFeeAMM::mintCall::SELECTOR => {
105 mutate::<ITIPFeeAMM::mintCall>(calldata, msg_sender, |s, call| {
106 if self.storage.spec().is_moderato() {
107 Err(TempoPrecompileError::UnknownFunctionSelector(
108 ITIPFeeAMM::mintCall::SELECTOR,
109 ))
110 } else {
111 self.mint(
112 s,
113 call.userToken,
114 call.validatorToken,
115 call.amountUserToken,
116 call.amountValidatorToken,
117 call.to,
118 )
119 }
120 })
121 }
122 ITIPFeeAMM::mintWithValidatorTokenCall::SELECTOR => {
123 mutate::<ITIPFeeAMM::mintWithValidatorTokenCall>(calldata, msg_sender, |s, call| {
124 self.mint_with_validator_token(
125 s,
126 call.userToken,
127 call.validatorToken,
128 call.amountValidatorToken,
129 call.to,
130 )
131 })
132 }
133 ITIPFeeAMM::burnCall::SELECTOR => {
134 mutate::<ITIPFeeAMM::burnCall>(calldata, msg_sender, |s, call| {
135 let (amount_user_token, amount_validator_token) = self.burn(
136 s,
137 call.userToken,
138 call.validatorToken,
139 call.liquidity,
140 call.to,
141 )?;
142
143 Ok(ITIPFeeAMM::burnReturn {
144 amountUserToken: amount_user_token,
145 amountValidatorToken: amount_validator_token,
146 })
147 })
148 }
149 ITIPFeeAMM::rebalanceSwapCall::SELECTOR => {
150 mutate::<ITIPFeeAMM::rebalanceSwapCall>(calldata, msg_sender, |s, call| {
151 self.rebalance_swap(
152 s,
153 call.userToken,
154 call.validatorToken,
155 call.amountOut,
156 call.to,
157 )
158 })
159 }
160
161 _ => unknown_selector(selector, self.storage.gas_used(), self.storage.spec()),
162 };
163
164 result.map(|res| fill_precompile_output(res, &mut self.storage))
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::{
172 Precompile, TIP_FEE_MANAGER_ADDRESS, expect_precompile_revert,
173 storage::{ContractStorage, StorageCtx, hashmap::HashMapStorageProvider},
174 test_util::{TIP20Setup, assert_full_coverage, check_selector_coverage},
175 tip_fee_manager::{
176 FeeManagerError,
177 amm::{MIN_LIQUIDITY, PoolKey},
178 },
179 };
180 use alloy::{
181 primitives::{Address, B256, Bytes, U256},
182 sol_types::{SolCall, SolError, SolValue},
183 };
184 use revm::precompile::PrecompileError;
185 use tempo_chainspec::hardfork::TempoHardfork;
186 use tempo_contracts::precompiles::{
187 IFeeManager::IFeeManagerCalls, ITIPFeeAMM::ITIPFeeAMMCalls, UnknownFunctionSelector,
188 };
189
190 #[test]
191 fn test_set_validator_token() -> eyre::Result<()> {
192 let mut storage = HashMapStorageProvider::new(1);
193 let admin = Address::random();
194 let validator = Address::random();
195 StorageCtx::enter(&mut storage, || {
196 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
197 let mut fee_manager = TipFeeManager::new();
198
199 let calldata = IFeeManager::setValidatorTokenCall {
200 token: token.address(),
201 }
202 .abi_encode();
203 let result = fee_manager.call(&calldata, validator)?;
204 assert_eq!(result.gas_used, 0);
205
206 let calldata = IFeeManager::validatorTokensCall { validator }.abi_encode();
208 let result = fee_manager.call(&calldata, validator)?;
209 assert_eq!(result.gas_used, 0);
210 let returned_token = Address::abi_decode(&result.bytes)?;
211 assert_eq!(returned_token, token.address());
212
213 Ok(())
214 })
215 }
216
217 #[test]
218 fn test_set_validator_token_zero_address() -> eyre::Result<()> {
219 let mut storage = HashMapStorageProvider::new(1);
220 let validator = Address::random();
221 StorageCtx::enter(&mut storage, || {
222 let mut fee_manager = TipFeeManager::new();
223
224 let calldata = IFeeManager::setValidatorTokenCall {
225 token: Address::ZERO,
226 }
227 .abi_encode();
228 let result = fee_manager.call(&calldata, validator);
229 expect_precompile_revert(&result, FeeManagerError::invalid_token());
230
231 Ok(())
232 })
233 }
234
235 #[test]
236 fn test_set_user_token() -> eyre::Result<()> {
237 let mut storage = HashMapStorageProvider::new(1);
238 let admin = Address::random();
239 let user = Address::random();
240 StorageCtx::enter(&mut storage, || {
241 let token = TIP20Setup::create("TestToken", "TST", admin).apply()?;
242 let mut fee_manager = TipFeeManager::new();
243
244 let calldata = IFeeManager::setUserTokenCall {
245 token: token.address(),
246 }
247 .abi_encode();
248 let result = fee_manager.call(&calldata, user)?;
249 assert_eq!(result.gas_used, 0);
250
251 let calldata = IFeeManager::userTokensCall { user }.abi_encode();
253 let result = fee_manager.call(&calldata, user)?;
254 assert_eq!(result.gas_used, 0);
255 let returned_token = Address::abi_decode(&result.bytes)?;
256 assert_eq!(returned_token, token.address());
257
258 Ok(())
259 })
260 }
261
262 #[test]
263 fn test_set_user_token_zero_address() -> eyre::Result<()> {
264 let mut storage = HashMapStorageProvider::new(1);
265 let user = Address::random();
266 StorageCtx::enter(&mut storage, || {
267 let mut fee_manager = TipFeeManager::new();
268
269 let calldata = IFeeManager::setUserTokenCall {
270 token: Address::ZERO,
271 }
272 .abi_encode();
273 let result = fee_manager.call(&calldata, user);
274 expect_precompile_revert(&result, FeeManagerError::invalid_token());
275
276 Ok(())
277 })
278 }
279
280 #[test]
281 fn test_get_pool_id() -> eyre::Result<()> {
282 let mut storage = HashMapStorageProvider::new(1);
283 let token_a = Address::random();
284 let token_b = Address::random();
285 let sender = Address::random();
286 StorageCtx::enter(&mut storage, || {
287 let mut fee_manager = TipFeeManager::new();
288
289 let calldata = ITIPFeeAMM::getPoolIdCall {
290 userToken: token_a,
291 validatorToken: token_b,
292 }
293 .abi_encode();
294 let result = fee_manager.call(&calldata, sender)?;
295 assert_eq!(result.gas_used, 0);
296
297 let returned_id = B256::abi_decode(&result.bytes)?;
298 let expected_id = PoolKey::new(token_a, token_b).get_id();
299 assert_eq!(returned_id, expected_id);
300
301 Ok(())
302 })
303 }
304
305 #[test]
306 fn test_tip_fee_amm_pool_operations() -> eyre::Result<()> {
307 let mut storage = HashMapStorageProvider::new(1);
308 let token_a = Address::random();
309 let token_b = Address::random();
310 let sender = Address::random();
311 StorageCtx::enter(&mut storage, || {
312 let mut fee_manager = TipFeeManager::new();
313
314 let get_pool_call = ITIPFeeAMM::getPoolCall {
316 userToken: token_a,
317 validatorToken: token_b,
318 };
319 let calldata = get_pool_call.abi_encode();
320 let result = fee_manager.call(&calldata, sender)?;
321 assert_eq!(result.gas_used, 0);
322
323 let pool = ITIPFeeAMM::Pool::abi_decode(&result.bytes)?;
325 assert_eq!(pool.reserveUserToken, 0);
326 assert_eq!(pool.reserveValidatorToken, 0);
327
328 Ok(())
329 })
330 }
331
332 #[test]
333 fn test_pool_id_calculation() -> eyre::Result<()> {
334 let mut storage = HashMapStorageProvider::new(1);
335 let token_a = Address::random();
336 let token_b = Address::random();
337 let sender = Address::random();
338 StorageCtx::enter(&mut storage, || {
339 let mut fee_manager = TipFeeManager::new();
340
341 let calldata1 = ITIPFeeAMM::getPoolIdCall {
343 userToken: token_a,
344 validatorToken: token_b,
345 }
346 .abi_encode();
347 let result1 = fee_manager.call(&calldata1, sender)?;
348 let id1 = B256::abi_decode(&result1.bytes)?;
349
350 let calldata2 = ITIPFeeAMM::getPoolIdCall {
352 userToken: token_b,
353 validatorToken: token_a,
354 }
355 .abi_encode();
356 let result2 = fee_manager.call(&calldata2, sender)?;
357 let id2 = B256::abi_decode(&result2.bytes)?;
358
359 assert_ne!(id1, id2);
361
362 Ok(())
363 })
364 }
365
366 #[test]
367 fn test_fee_manager_invalid_token_error() -> eyre::Result<()> {
368 let mut storage = HashMapStorageProvider::new(1);
369 let user = Address::random();
370 let validator = Address::random();
371 StorageCtx::enter(&mut storage, || {
372 let mut fee_manager = TipFeeManager::new();
373
374 let set_validator_call = IFeeManager::setValidatorTokenCall {
376 token: Address::ZERO,
377 };
378 let result = fee_manager.call(&set_validator_call.abi_encode(), validator);
379 expect_precompile_revert(&result, FeeManagerError::invalid_token());
380
381 let set_user_call = IFeeManager::setUserTokenCall {
383 token: Address::ZERO,
384 };
385 let result = fee_manager.call(&set_user_call.abi_encode(), user);
386 expect_precompile_revert(&result, FeeManagerError::invalid_token());
387
388 Ok(())
389 })
390 }
391
392 #[test]
393 fn test_execute_block() -> eyre::Result<()> {
394 let mut storage = HashMapStorageProvider::new(1);
395 StorageCtx::enter(&mut storage, || {
396 let mut fee_manager = TipFeeManager::new();
397
398 let call = IFeeManager::executeBlockCall {};
400 let result = fee_manager.call(&call.abi_encode(), Address::ZERO)?;
401 assert_eq!(result.gas_used, 0);
402
403 Ok(())
404 })
405 }
406
407 #[test]
408 fn test_tip_fee_manager_selector_coverage() -> eyre::Result<()> {
409 let mut storage = HashMapStorageProvider::new(1);
410 StorageCtx::enter(&mut storage, || {
411 let mut fee_manager = TipFeeManager::new();
412
413 let fee_manager_unsupported = check_selector_coverage(
414 &mut fee_manager,
415 IFeeManagerCalls::SELECTORS,
416 "IFeeManager",
417 IFeeManagerCalls::name_by_selector,
418 );
419
420 let amm_unsupported = check_selector_coverage(
421 &mut fee_manager,
422 ITIPFeeAMMCalls::SELECTORS,
423 "ITIPFeeAMM",
424 ITIPFeeAMMCalls::name_by_selector,
425 );
426
427 assert_full_coverage([fee_manager_unsupported, amm_unsupported]);
428
429 Ok(())
430 })
431 }
432
433 #[test]
434 fn test_mint_with_validator_token() -> eyre::Result<()> {
435 let mut storage = HashMapStorageProvider::new(1);
436 let admin = Address::random();
437 let user = Address::random();
438 StorageCtx::enter(&mut storage, || {
439 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
440 .with_issuer(admin)
441 .with_mint(user, U256::from(1000000_u64))
442 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
443 .apply()?;
444
445 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
446 .with_issuer(admin)
447 .with_mint(user, U256::from(1000000_u64))
448 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
449 .apply()?;
450
451 let mut fee_manager = TipFeeManager::new();
452
453 let pool_id_call = ITIPFeeAMM::getPoolIdCall {
455 userToken: user_token.address(),
456 validatorToken: validator_token.address(),
457 };
458 let pool_id_result = fee_manager.call(&pool_id_call.abi_encode(), user)?;
459 let pool_id = B256::abi_decode(&pool_id_result.bytes)?;
460
461 let initial_supply_call = ITIPFeeAMM::totalSupplyCall { poolId: pool_id };
463 let initial_supply_result =
464 fee_manager.call(&initial_supply_call.abi_encode(), user)?;
465 let initial_supply = U256::abi_decode(&initial_supply_result.bytes)?;
466 assert_eq!(initial_supply, U256::ZERO);
467
468 let amount_validator_token = U256::from(10000_u64);
470 let call = ITIPFeeAMM::mintWithValidatorTokenCall {
471 userToken: user_token.address(),
472 validatorToken: validator_token.address(),
473 amountValidatorToken: amount_validator_token,
474 to: user,
475 };
476 let result = fee_manager.call(&call.abi_encode(), user)?;
477
478 let liquidity = U256::abi_decode(&result.bytes)?;
481 assert_eq!(liquidity, U256::from(4000_u64));
482
483 let final_supply_call = ITIPFeeAMM::totalSupplyCall { poolId: pool_id };
485 let final_supply_result = fee_manager.call(&final_supply_call.abi_encode(), user)?;
486 let final_supply = U256::abi_decode(&final_supply_result.bytes)?;
487 assert_eq!(final_supply, liquidity + MIN_LIQUIDITY);
488
489 let pool_call = ITIPFeeAMM::getPoolCall {
491 userToken: user_token.address(),
492 validatorToken: validator_token.address(),
493 };
494 let pool_result = fee_manager.call(&pool_call.abi_encode(), user)?;
495 let pool = ITIPFeeAMM::Pool::abi_decode(&pool_result.bytes)?;
496 assert_eq!(pool.reserveUserToken, 0);
497 assert_eq!(pool.reserveValidatorToken, 10000);
498
499 let balance_call = ITIPFeeAMM::liquidityBalancesCall {
501 poolId: pool_id,
502 user,
503 };
504 let balance_result = fee_manager.call(&balance_call.abi_encode(), user)?;
505 let balance = U256::abi_decode(&balance_result.bytes)?;
506 assert_eq!(balance, liquidity);
507
508 Ok(())
509 })
510 }
511
512 #[test]
513 fn test_unknown_selector_error_pre_moderato() -> eyre::Result<()> {
514 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Adagio);
515 let sender = Address::random();
516 StorageCtx::enter(&mut storage, || {
517 let mut fee_manager = TipFeeManager::new();
518
519 let unknown_selector = [0x12, 0x34, 0x56, 0x78];
521 let calldata = Bytes::from(unknown_selector);
522 let result = fee_manager.call(&calldata, sender);
523
524 assert!(result.is_err());
526 assert!(matches!(result, Err(PrecompileError::Other(_))));
527
528 Ok(())
529 })
530 }
531
532 #[test]
533 fn test_unknown_selector_error_post_moderato() -> eyre::Result<()> {
534 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Moderato);
535 let sender = Address::random();
536 StorageCtx::enter(&mut storage, || {
537 let mut fee_manager = TipFeeManager::new();
538
539 let unknown_selector = [0x12, 0x34, 0x56, 0x78];
541 let calldata = Bytes::from(unknown_selector);
542 let result = fee_manager.call(&calldata, sender);
543
544 assert!(result.is_ok());
546 let output = result.unwrap();
547 assert!(output.reverted);
548
549 let decoded_error = UnknownFunctionSelector::abi_decode(&output.bytes);
551 assert!(
552 decoded_error.is_ok(),
553 "Should decode as UnknownFunctionSelector"
554 );
555
556 let error = decoded_error.unwrap();
558 assert_eq!(error.selector.as_slice(), &unknown_selector);
559
560 Ok(())
561 })
562 }
563
564 #[test]
565 fn test_mint_deprecated_post_moderato() -> eyre::Result<()> {
566 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Moderato);
567 let admin = Address::random();
568 let user = Address::random();
569 StorageCtx::enter(&mut storage, || {
570 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
571 .with_issuer(admin)
572 .with_mint(user, U256::from(1000000_u64))
573 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
574 .apply()?;
575
576 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
577 .with_issuer(admin)
578 .with_mint(user, U256::from(1000000_u64))
579 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
580 .apply()?;
581
582 let mut fee_manager = TipFeeManager::new();
583
584 let call = ITIPFeeAMM::mintCall {
585 userToken: user_token.address(),
586 validatorToken: validator_token.address(),
587 amountUserToken: U256::from(1000_u64),
588 amountValidatorToken: U256::from(1000_u64),
589 to: user,
590 };
591
592 let result = fee_manager.call(&call.abi_encode(), user);
593
594 assert!(result.is_ok());
596 let output = result.unwrap();
597 assert!(output.reverted);
598
599 let decoded_error = UnknownFunctionSelector::abi_decode(&output.bytes);
601 assert!(
602 decoded_error.is_ok(),
603 "Should decode as UnknownFunctionSelector"
604 );
605
606 let error = decoded_error.unwrap();
608 assert_eq!(error.selector.as_slice(), &ITIPFeeAMM::mintCall::SELECTOR);
609
610 Ok(())
611 })
612 }
613}