1pub mod amm;
2pub mod dispatch;
3
4use alloy::primitives::B256;
5use tempo_contracts::precompiles::TIP_FEE_MANAGER_ADDRESS;
6pub use tempo_contracts::precompiles::{
7 FeeManagerError, FeeManagerEvent, IFeeManager, ITIPFeeAMM, TIPFeeAMMError, TIPFeeAMMEvent,
8};
9
10use crate::{
11 DEFAULT_FEE_TOKEN_POST_ALLEGRETTO, DEFAULT_FEE_TOKEN_PRE_ALLEGRETTO, PATH_USD_ADDRESS,
12 error::{Result, TempoPrecompileError},
13 storage::{Mapping, PrecompileStorageProvider, Slot, StorageKey, VecSlotExt},
14 tip_fee_manager::amm::{Pool, compute_amount_out},
15 tip20::{
16 ITIP20, TIP20Token, address_to_token_id_unchecked, is_tip20_prefix, token_id_to_address,
17 validate_usd_currency,
18 },
19};
20
21use alloy::primitives::{Address, Bytes, IntoLogData, U256, uint};
23use revm::state::Bytecode;
24use tempo_precompiles_macros::{Storable, contract};
25
26type TokensWithFees = Slot<Vec<Address>>;
28
29type PoolsWithFees = Slot<Vec<TokenPair>>;
31
32type ValidatorsWithFees = Slot<Vec<Address>>;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Storable)]
36pub struct TokenPair {
37 pub user_token: u64,
38 pub validator_token: u64,
39}
40
41impl StorageKey for TokenPair {
42 fn as_storage_bytes(&self) -> impl AsRef<[u8]> {
43 let mut bytes = [0u8; 16];
44 bytes[..8].copy_from_slice(&self.user_token.to_be_bytes());
45 bytes[8..16].copy_from_slice(&self.validator_token.to_be_bytes());
46 bytes
47 }
48}
49
50#[contract]
51pub struct TipFeeManager {
52 validator_tokens: Mapping<Address, Address>,
53 user_tokens: Mapping<Address, Address>,
54 collected_fees: Mapping<Address, U256>,
55 tokens_with_fees: Vec<Address>,
56 token_in_fees_array: Mapping<Address, bool>,
57 pools: Mapping<B256, Pool>,
58 pending_fee_swap_in: Mapping<B256, u128>,
59 total_supply: Mapping<B256, U256>,
60 liquidity_balances: Mapping<B256, Mapping<Address, U256>>,
61 pools_with_fees: Vec<TokenPair>,
62 pool_in_fees_array: Mapping<TokenPair, bool>,
63 validators_with_fees: Vec<Address>,
64 validator_in_fees_array: Mapping<Address, bool>,
65}
66
67impl<'a, S: PrecompileStorageProvider> TipFeeManager<'a, S> {
68 pub const FEE_BPS: u64 = 25; pub const BASIS_POINTS: u64 = 10000;
71 pub const MINIMUM_BALANCE: U256 = uint!(1_000_000_000_U256); pub fn new(storage: &'a mut S) -> Self {
77 Self::_new(TIP_FEE_MANAGER_ADDRESS, storage)
78 }
79
80 pub fn initialize(&mut self) -> Result<()> {
84 self.storage.set_code(
86 self.address,
87 Bytecode::new_legacy(Bytes::from_static(&[0xef])),
88 )
89 }
90
91 fn default_fee_token(&self) -> Address {
94 if self.storage.spec().is_allegretto() {
95 DEFAULT_FEE_TOKEN_POST_ALLEGRETTO
96 } else {
97 DEFAULT_FEE_TOKEN_PRE_ALLEGRETTO
98 }
99 }
100
101 pub fn get_validator_token(&mut self, beneficiary: Address) -> Result<Address> {
102 let token = self.sload_validator_tokens(beneficiary)?;
103
104 if token.is_zero() {
105 Ok(self.default_fee_token())
106 } else {
107 Ok(token)
108 }
109 }
110
111 pub fn set_validator_token(
112 &mut self,
113 sender: Address,
114 call: IFeeManager::setValidatorTokenCall,
115 beneficiary: Address,
116 ) -> Result<()> {
117 if !is_tip20_prefix(call.token) {
118 return Err(FeeManagerError::invalid_token().into());
119 }
120
121 if self.storage.spec().is_allegretto() && self.sload_validator_in_fees_array(sender)? {
123 return Err(FeeManagerError::cannot_change_with_pending_fees().into());
124 }
125
126 if sender == beneficiary {
128 return Err(FeeManagerError::cannot_change_within_block().into());
129 }
130
131 validate_usd_currency(call.token, self.storage)?;
133
134 self.sstore_validator_tokens(sender, call.token)?;
135
136 self.storage.emit_event(
138 self.address,
139 FeeManagerEvent::ValidatorTokenSet(IFeeManager::ValidatorTokenSet {
140 validator: sender,
141 token: call.token,
142 })
143 .into_log_data(),
144 )
145 }
146
147 pub fn set_user_token(
148 &mut self,
149 sender: Address,
150 call: IFeeManager::setUserTokenCall,
151 ) -> Result<()> {
152 if !is_tip20_prefix(call.token) {
153 return Err(FeeManagerError::invalid_token().into());
154 }
155
156 if self.storage.spec().is_moderato()
161 && !self.storage.spec().is_allegro_moderato()
162 && call.token == PATH_USD_ADDRESS
163 {
164 return Err(FeeManagerError::invalid_token().into());
165 }
166
167 validate_usd_currency(call.token, self.storage)?;
169
170 self.sstore_user_tokens(sender, call.token)?;
171
172 self.storage.emit_event(
174 self.address,
175 FeeManagerEvent::UserTokenSet(IFeeManager::UserTokenSet {
176 user: sender,
177 token: call.token,
178 })
179 .into_log_data(),
180 )
181 }
182
183 pub fn collect_fee_pre_tx(
189 &mut self,
190 fee_payer: Address,
191 user_token: Address,
192 max_amount: U256,
193 beneficiary: Address,
194 ) -> Result<Address> {
195 let validator_token = self.get_validator_token(beneficiary)?;
197
198 if user_token != validator_token {
200 self.reserve_liquidity(user_token, validator_token, max_amount)?;
201 }
202
203 let mut tip20_token = TIP20Token::from_address(user_token, self.storage)?;
204
205 tip20_token.ensure_transfer_authorized(fee_payer, self.address)?;
207 tip20_token.transfer_fee_pre_tx(fee_payer, max_amount)?;
208
209 Ok(user_token)
211 }
212
213 pub fn collect_fee_post_tx(
218 &mut self,
219 fee_payer: Address,
220 actual_spending: U256,
221 refund_amount: U256,
222 fee_token: Address,
223 beneficiary: Address,
224 ) -> Result<()> {
225 let mut tip20_token = TIP20Token::from_address(fee_token, self.storage)?;
227 tip20_token.transfer_fee_post_tx(fee_payer, refund_amount, actual_spending)?;
228
229 let validator_token = self.get_validator_token(beneficiary)?;
231
232 if fee_token != validator_token {
233 self.release_liquidity(fee_token, validator_token, refund_amount)?;
235
236 if !actual_spending.is_zero() {
238 if !self.storage.spec().is_allegretto() {
239 if !self.sload_token_in_fees_array(fee_token)? {
241 TokensWithFees::new(slots::TOKENS_WITH_FEES).push(self, fee_token)?;
242 self.sstore_token_in_fees_array(fee_token, true)?;
243 }
244 } else {
245 self.add_pair_to_fees_array(fee_token, validator_token)?;
246 }
247 }
248 }
249
250 if !self.storage.spec().is_allegretto() {
251 if fee_token == validator_token {
253 self.increment_collected_fees(beneficiary, actual_spending)?;
254 }
255 } else {
256 let amount = if fee_token == validator_token {
258 actual_spending
259 } else {
260 compute_amount_out(actual_spending)?
261 };
262
263 self.increment_collected_fees(beneficiary, amount)?;
264 }
265
266 Ok(())
267 }
268
269 pub fn execute_block(&mut self, sender: Address, beneficiary: Address) -> Result<()> {
270 if sender != Address::ZERO {
272 return Err(FeeManagerError::only_system_contract().into());
273 }
274
275 let mut total_amount_out = U256::ZERO;
276 let pools = if !self.storage.spec().is_allegretto() {
277 let validator_token = self.get_validator_token(beneficiary)?;
278 self.drain_tokens_with_fees()?
279 .into_iter()
280 .map(|token| (token, validator_token))
281 .collect::<Vec<_>>()
282 } else {
283 self.drain_pools_with_fees()?
284 .into_iter()
285 .map(|pair| {
286 (
287 token_id_to_address(pair.user_token),
288 token_id_to_address(pair.validator_token),
289 )
290 })
291 .collect()
292 };
293 for (user_token, validator_token) in pools {
294 total_amount_out += self.execute_pending_fee_swaps(user_token, validator_token)?;
295 }
296
297 if !self.storage.spec().is_allegretto() && !total_amount_out.is_zero() {
299 self.increment_collected_fees(beneficiary, total_amount_out)?;
300 }
301
302 for validator in self.drain_validators_with_fees()? {
303 let collected_fees = self.sload_collected_fees(validator)?;
304
305 if collected_fees.is_zero() {
306 continue;
307 }
308
309 let validator_token = self.get_validator_token(validator)?;
310 let mut token = TIP20Token::from_address(validator_token, self.storage)?;
311
312 if token.is_transfer_authorized(self.address, beneficiary)? {
314 let balance = token.balance_of(ITIP20::balanceOfCall {
316 account: self.address,
317 })?;
318
319 if !balance.is_zero() {
320 token
321 .transfer(
322 self.address,
323 ITIP20::transferCall {
324 to: beneficiary,
325 amount: collected_fees.min(balance),
326 },
327 )
328 .map_err(|_| {
329 IFeeManager::IFeeManagerErrors::InsufficientFeeTokenBalance(
330 IFeeManager::InsufficientFeeTokenBalance {},
331 )
332 })?;
333 }
334 }
335
336 self.sstore_collected_fees(validator, U256::ZERO)?;
338 }
339
340 Ok(())
341 }
342
343 fn add_pair_to_fees_array(
345 &mut self,
346 user_token: Address,
347 validator_token: Address,
348 ) -> Result<()> {
349 let pair = TokenPair {
350 user_token: address_to_token_id_unchecked(user_token),
351 validator_token: address_to_token_id_unchecked(validator_token),
352 };
353 if !self.sload_pool_in_fees_array(pair)? {
354 self.sstore_pool_in_fees_array(pair, true)?;
355 PoolsWithFees::new(slots::POOLS_WITH_FEES).push(self, pair)?;
356 }
357 Ok(())
358 }
359
360 fn drain_tokens_with_fees(&mut self) -> Result<Vec<Address>> {
364 let mut tokens = Vec::new();
365 let tokens_with_fees = TokensWithFees::new(slots::TOKENS_WITH_FEES);
366 while let Some(token) = tokens_with_fees.pop(self)? {
367 tokens.push(token);
368 if self.storage.spec().is_moderato() {
369 self.sstore_token_in_fees_array(token, false)?;
370 }
371 }
372
373 Ok(tokens)
374 }
375
376 fn drain_validators_with_fees(&mut self) -> Result<Vec<Address>> {
378 let mut validators = Vec::new();
379 let validator_with_fees = ValidatorsWithFees::new(slots::VALIDATORS_WITH_FEES);
380 while let Some(validator) = validator_with_fees.pop(self)? {
381 validators.push(validator);
382 self.sstore_validator_in_fees_array(validator, false)?;
383 }
384 Ok(validators)
385 }
386
387 fn drain_pools_with_fees(&mut self) -> Result<Vec<TokenPair>> {
389 let mut pools = Vec::new();
390 let pools_with_fees = PoolsWithFees::new(slots::POOLS_WITH_FEES);
391 while let Some(pool) = pools_with_fees.pop(self)? {
392 pools.push(pool);
393 self.sstore_pool_in_fees_array(pool, false)?;
394 }
395 Ok(pools)
396 }
397
398 fn increment_collected_fees(&mut self, validator: Address, amount: U256) -> Result<()> {
400 if amount.is_zero() {
401 return Ok(());
402 }
403
404 let collected_fees = self.sload_collected_fees(validator)?;
405 self.sstore_collected_fees(
406 validator,
407 collected_fees
408 .checked_add(amount)
409 .ok_or(TempoPrecompileError::under_overflow())?,
410 )?;
411
412 if collected_fees.is_zero() {
414 self.sstore_validator_in_fees_array(validator, true)?;
415 ValidatorsWithFees::new(slots::VALIDATORS_WITH_FEES).push(self, validator)?;
416 }
417
418 Ok(())
419 }
420
421 pub fn user_tokens(&mut self, call: IFeeManager::userTokensCall) -> Result<Address> {
422 self.sload_user_tokens(call.user)
423 }
424
425 pub fn validator_tokens(&mut self, call: IFeeManager::validatorTokensCall) -> Result<Address> {
426 let token = self.sload_validator_tokens(call.validator)?;
427
428 if token.is_zero() {
429 Ok(self.default_fee_token())
430 } else {
431 Ok(token)
432 }
433 }
434
435 pub fn get_fee_token_balance(
436 &mut self,
437 call: IFeeManager::getFeeTokenBalanceCall,
438 ) -> Result<IFeeManager::getFeeTokenBalanceReturn> {
439 let mut token = self.sload_user_tokens(call.sender)?;
440
441 if token.is_zero() {
442 let validator_token = self.sload_validator_tokens(call.validator)?;
443
444 if validator_token.is_zero() {
445 return Ok(IFeeManager::getFeeTokenBalanceReturn {
446 _0: Address::ZERO,
447 _1: U256::ZERO,
448 });
449 } else {
450 token = validator_token;
451 }
452 }
453
454 let mut tip20_token = TIP20Token::from_address(token, self.storage)?;
455 let token_balance = tip20_token.balance_of(ITIP20::balanceOfCall {
456 account: call.sender,
457 })?;
458
459 Ok(IFeeManager::getFeeTokenBalanceReturn {
460 _0: token,
461 _1: token_balance,
462 })
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use tempo_chainspec::hardfork::TempoHardfork;
469 use tempo_contracts::precompiles::TIP20Error;
470
471 use super::*;
472 use crate::{
473 PATH_USD_ADDRESS, TIP_FEE_MANAGER_ADDRESS,
474 error::TempoPrecompileError,
475 storage::hashmap::HashMapStorageProvider,
476 tip20::{ISSUER_ROLE, ITIP20, TIP20Token, tests::initialize_path_usd, token_id_to_address},
477 };
478
479 fn deploy_token_with_balance(
480 storage: &mut HashMapStorageProvider,
481 token: Address,
482 user: Address,
483 amount: U256,
484 ) {
485 initialize_path_usd(storage, user).unwrap();
486 let mut tip20_token = TIP20Token::from_address(token, storage).unwrap();
487
488 tip20_token
490 .initialize(
491 "TestToken",
492 "TEST",
493 "USD",
494 PATH_USD_ADDRESS,
495 user,
496 Address::ZERO,
497 )
498 .unwrap();
499
500 tip20_token.grant_role_internal(user, *ISSUER_ROLE).unwrap();
502
503 tip20_token
504 .mint(user, ITIP20::mintCall { to: user, amount })
505 .unwrap();
506
507 tip20_token
509 .approve(
510 user,
511 ITIP20::approveCall {
512 spender: TIP_FEE_MANAGER_ADDRESS,
513 amount: U256::MAX,
514 },
515 )
516 .unwrap();
517 }
518
519 #[test]
520 fn test_set_user_token() -> eyre::Result<()> {
521 let mut storage = HashMapStorageProvider::new(1);
522 let user = Address::random();
523
524 initialize_path_usd(&mut storage, user).unwrap();
526
527 let token = token_id_to_address(1);
529 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
530 tip20_token
531 .initialize(
532 "TestToken",
533 "TEST",
534 "USD",
535 PATH_USD_ADDRESS,
536 user,
537 Address::ZERO,
538 )
539 .unwrap();
540
541 let mut fee_manager = TipFeeManager::new(&mut storage);
542
543 let call = IFeeManager::setUserTokenCall { token };
544 let result = fee_manager.set_user_token(user, call);
545 assert!(result.is_ok());
546
547 let call = IFeeManager::userTokensCall { user };
548 assert_eq!(fee_manager.user_tokens(call)?, token);
549 Ok(())
550 }
551
552 #[test]
553 fn test_set_user_token_cannot_be_path_usd_post_moderato() -> eyre::Result<()> {
554 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Moderato);
556 let user = Address::random();
557
558 initialize_path_usd(&mut storage, user).unwrap();
560
561 let mut fee_manager = TipFeeManager::new(&mut storage);
562
563 let call = IFeeManager::setUserTokenCall {
565 token: PATH_USD_ADDRESS,
566 };
567 let result = fee_manager.set_user_token(user, call);
568
569 assert!(matches!(
570 result,
571 Err(TempoPrecompileError::FeeManagerError(
572 FeeManagerError::InvalidToken(_)
573 ))
574 ));
575
576 Ok(())
577 }
578
579 #[test]
580 fn test_set_user_token_allows_path_usd_pre_moderato() -> eyre::Result<()> {
581 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::Adagio);
583 let user = Address::random();
584
585 initialize_path_usd(&mut storage, user).unwrap();
587
588 let mut fee_manager = TipFeeManager::new(&mut storage);
589
590 let call = IFeeManager::setUserTokenCall {
592 token: PATH_USD_ADDRESS,
593 };
594 let result = fee_manager.set_user_token(user, call);
595
596 assert!(result.is_ok());
598
599 Ok(())
600 }
601
602 #[test]
603 fn test_set_user_token_allows_path_usd_post_allegro_moderato() -> eyre::Result<()> {
604 let mut storage = HashMapStorageProvider::new(1).with_spec(TempoHardfork::AllegroModerato);
605 let user = Address::random();
606 initialize_path_usd(&mut storage, user).unwrap();
607
608 let mut fee_manager = TipFeeManager::new(&mut storage);
609
610 let call = IFeeManager::setUserTokenCall {
611 token: PATH_USD_ADDRESS,
612 };
613 let result = fee_manager.set_user_token(user, call);
614
615 assert!(result.is_ok());
617
618 Ok(())
619 }
620
621 #[test]
622 fn test_set_validator_token() -> eyre::Result<()> {
623 let mut storage = HashMapStorageProvider::new(1);
624 let validator = Address::random();
625 let admin = Address::random();
626
627 initialize_path_usd(&mut storage, admin).unwrap();
629
630 let token = token_id_to_address(1);
632 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
633 tip20_token
634 .initialize(
635 "TestToken",
636 "TEST",
637 "USD",
638 PATH_USD_ADDRESS,
639 admin,
640 Address::ZERO,
641 )
642 .unwrap();
643
644 let mut fee_manager = TipFeeManager::new(&mut storage);
645
646 let call = IFeeManager::setValidatorTokenCall { token };
647 let result = fee_manager.set_validator_token(validator, call.clone(), validator);
648 assert_eq!(
649 result,
650 Err(TempoPrecompileError::FeeManagerError(
651 FeeManagerError::cannot_change_within_block()
652 ))
653 );
654
655 let beneficiary = Address::random();
657 let result = fee_manager.set_validator_token(validator, call, beneficiary);
658 assert!(result.is_ok());
659
660 let query_call = IFeeManager::validatorTokensCall { validator };
661 let returned_token = fee_manager.validator_tokens(query_call)?;
662 assert_eq!(returned_token, token);
663
664 Ok(())
665 }
666
667 #[test]
668 fn test_set_validator_token_cannot_change_with_pending_fees() -> eyre::Result<()> {
669 use tempo_chainspec::hardfork::TempoHardfork;
670
671 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::Allegretto);
673 let validator = Address::random();
674 let beneficiary = Address::random(); let admin = Address::random();
676
677 initialize_path_usd(&mut storage, admin).unwrap();
679
680 let token = token_id_to_address(1);
682 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
683 tip20_token
684 .initialize(
685 "TestToken",
686 "TEST",
687 "USD",
688 PATH_USD_ADDRESS,
689 admin,
690 Address::ZERO,
691 )
692 .unwrap();
693
694 let mut fee_manager = TipFeeManager::new(&mut storage);
695
696 fee_manager.sstore_validator_in_fees_array(validator, true)?;
698
699 let call = IFeeManager::setValidatorTokenCall { token };
701 let result = fee_manager.set_validator_token(validator, call.clone(), beneficiary);
702
703 assert_eq!(
705 result,
706 Err(TempoPrecompileError::FeeManagerError(
707 FeeManagerError::cannot_change_with_pending_fees()
708 ))
709 );
710
711 fee_manager.sstore_validator_in_fees_array(validator, false)?;
713 let result = fee_manager.set_validator_token(validator, call.clone(), beneficiary);
714 assert!(result.is_ok());
715
716 let result = fee_manager.set_validator_token(validator, call, validator);
718 assert_eq!(
719 result,
720 Err(TempoPrecompileError::FeeManagerError(
721 FeeManagerError::cannot_change_within_block()
722 ))
723 );
724
725 Ok(())
726 }
727
728 #[test]
729 fn test_is_tip20_prefix() {
730 let token_id = rand::random::<u64>();
731 let token = token_id_to_address(token_id);
732 assert!(is_tip20_prefix(token));
733
734 let token = Address::random();
735 assert!(!is_tip20_prefix(token));
736 }
737
738 #[test]
739 fn test_collect_fee_pre_tx() {
740 let mut storage = HashMapStorageProvider::new(1);
741 let user = Address::random();
742 let validator = Address::random();
743 let token = token_id_to_address(rand::random::<u64>());
744 let max_amount = U256::from(10000);
745
746 deploy_token_with_balance(&mut storage, token, user, U256::from(u64::MAX));
748
749 let mut fee_manager = TipFeeManager::new(&mut storage);
750
751 let beneficiary = Address::random();
754 fee_manager
755 .set_validator_token(
756 validator,
757 IFeeManager::setValidatorTokenCall { token },
758 beneficiary,
759 )
760 .unwrap();
761
762 fee_manager
764 .set_user_token(user, IFeeManager::setUserTokenCall { token })
765 .unwrap();
766
767 let result = fee_manager.collect_fee_pre_tx(user, token, max_amount, validator);
769 assert!(result.is_ok());
770 assert_eq!(result.unwrap(), token);
771 }
772
773 #[test]
774 fn test_collect_fee_post_tx() -> eyre::Result<()> {
775 let mut storage = HashMapStorageProvider::new(1);
776 let user = Address::random();
777 let token = token_id_to_address(rand::random::<u64>());
778 let actual_used = U256::from(6000);
779 let refund_amount = U256::from(4000);
780
781 let admin = Address::random();
783
784 {
786 initialize_path_usd(&mut storage, admin).unwrap();
787 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
788 tip20_token
789 .initialize(
790 "TestToken",
791 "TEST",
792 "USD",
793 PATH_USD_ADDRESS,
794 admin,
795 Address::ZERO,
796 )
797 .unwrap();
798
799 tip20_token.grant_role_internal(admin, *ISSUER_ROLE)?;
800 tip20_token
801 .mint(
802 admin,
803 ITIP20::mintCall {
804 to: TIP_FEE_MANAGER_ADDRESS,
805 amount: U256::from(100000000000000_u64),
806 },
807 )
808 .unwrap();
809 }
810
811 let validator = Address::random();
812 let mut fee_manager = TipFeeManager::new(&mut storage);
813
814 fee_manager
817 .set_validator_token(
818 validator,
819 IFeeManager::setValidatorTokenCall { token },
820 Address::random(),
821 )
822 .unwrap();
823
824 fee_manager
826 .set_user_token(user, IFeeManager::setUserTokenCall { token })
827 .unwrap();
828
829 let result =
831 fee_manager.collect_fee_post_tx(user, actual_used, refund_amount, token, validator);
832 assert!(result.is_ok());
833
834 let tracked_amount = fee_manager.sload_collected_fees(validator)?;
836 assert_eq!(tracked_amount, actual_used);
837
838 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
840 let balance = tip20_token.balance_of(ITIP20::balanceOfCall { account: user })?;
841 assert_eq!(balance, refund_amount);
842
843 Ok(())
844 }
845
846 #[test]
847 fn test_rejects_non_usd() -> eyre::Result<()> {
848 let mut storage = HashMapStorageProvider::new(1);
849
850 let admin = Address::random();
851 let token = token_id_to_address(rand::random::<u64>());
852 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
853 tip20_token
854 .initialize(
855 "NonUSD",
856 "NonUSD",
857 "NonUSD",
858 PATH_USD_ADDRESS,
859 admin,
860 Address::ZERO,
861 )
862 .unwrap();
863
864 let validator = Address::random();
865 let mut fee_manager = TipFeeManager::new(&mut storage);
866
867 let user = Address::random();
868
869 let call = IFeeManager::setUserTokenCall { token };
870 let result = fee_manager.set_user_token(user, call);
871
872 assert!(matches!(
873 result,
874 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
875 ));
876
877 let call = IFeeManager::setValidatorTokenCall { token };
879 let result = fee_manager.set_validator_token(validator, call, Address::random());
880
881 assert!(matches!(
882 result,
883 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
884 ));
885
886 Ok(())
887 }
888
889 #[test]
890 fn test_prevent_insufficient_balance_transfer() -> eyre::Result<()> {
891 let mut storage = HashMapStorageProvider::new(1);
892 let admin = Address::random();
893 let validator = Address::random();
894 let token = token_id_to_address(rand::random::<u64>());
895
896 let collected_fees = U256::from(1000);
898 let balance = U256::from(500);
899
900 {
901 initialize_path_usd(&mut storage, admin)?;
903 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
904 tip20_token.initialize(
905 "TestToken",
906 "TEST",
907 "USD",
908 PATH_USD_ADDRESS,
909 admin,
910 Address::ZERO,
911 )?;
912 tip20_token.grant_role_internal(admin, *ISSUER_ROLE)?;
913
914 tip20_token.mint(
916 admin,
917 ITIP20::mintCall {
918 to: TIP_FEE_MANAGER_ADDRESS,
919 amount: balance,
920 },
921 )?;
922 }
923
924 {
925 let mut fee_manager = TipFeeManager::new(&mut storage);
927 fee_manager.set_validator_token(
928 validator,
929 IFeeManager::setValidatorTokenCall { token },
930 Address::random(),
931 )?;
932
933 fee_manager.increment_collected_fees(validator, collected_fees)?;
935
936 let result = fee_manager.execute_block(Address::ZERO, validator);
938 assert!(result.is_ok());
939
940 let remaining_fees = fee_manager.sload_collected_fees(validator)?;
942 assert_eq!(remaining_fees, U256::ZERO);
943 }
944
945 let mut tip20_token = TIP20Token::from_address(token, &mut storage).unwrap();
947 let validator_balance =
948 tip20_token.balance_of(ITIP20::balanceOfCall { account: validator })?;
949 assert_eq!(validator_balance, balance);
950
951 let fee_manager_balance = tip20_token.balance_of(ITIP20::balanceOfCall {
952 account: TIP_FEE_MANAGER_ADDRESS,
953 })?;
954 assert_eq!(fee_manager_balance, U256::ZERO);
955
956 Ok(())
957 }
958}