1pub mod amm;
6pub mod dispatch;
7
8use crate::{
9 error::{Result, TempoPrecompileError},
10 storage::{Handler, Mapping},
11 tip_fee_manager::amm::{Pool, PoolKey, compute_amount_out},
12 tip20::{ITIP20, TIP20Token, validate_usd_currency},
13 tip20_factory::TIP20Factory,
14};
15use alloy::primitives::{Address, B256, U256, uint};
16pub use tempo_contracts::precompiles::{
17 DEFAULT_FEE_TOKEN, FeeManagerError, FeeManagerEvent, IFeeManager, ITIPFeeAMM,
18 TIP_FEE_MANAGER_ADDRESS, TIPFeeAMMError, TIPFeeAMMEvent,
19};
20use tempo_precompiles_macros::contract;
21
22#[contract(addr = TIP_FEE_MANAGER_ADDRESS)]
30pub struct TipFeeManager {
31 validator_tokens: Mapping<Address, Address>,
32 user_tokens: Mapping<Address, Address>,
33 collected_fees: Mapping<Address, Mapping<Address, U256>>,
34 pools: Mapping<B256, Pool>,
35 total_supply: Mapping<B256, U256>,
36 liquidity_balances: Mapping<B256, Mapping<Address, U256>>,
37
38 pending_fee_swap_reservation: Mapping<B256, u128>,
44}
45
46impl TipFeeManager {
47 pub const FEE_BPS: u64 = 25;
49 pub const BASIS_POINTS: u64 = 10000;
51 pub const MINIMUM_BALANCE: U256 = uint!(1_000_000_000_U256);
53
54 pub fn initialize(&mut self) -> Result<()> {
56 self.__initialize()
57 }
58
59 pub fn get_validator_token(&self, beneficiary: Address) -> Result<Address> {
61 let token = self.validator_tokens[beneficiary].read()?;
62
63 if token.is_zero() {
64 Ok(DEFAULT_FEE_TOKEN)
65 } else {
66 Ok(token)
67 }
68 }
69
70 pub fn set_validator_token(
81 &mut self,
82 sender: Address,
83 call: IFeeManager::setValidatorTokenCall,
84 beneficiary: Address,
85 ) -> Result<()> {
86 if !TIP20Factory::new().is_tip20(call.token)? {
88 return Err(FeeManagerError::invalid_token().into());
89 }
90
91 if sender == beneficiary {
93 return Err(FeeManagerError::cannot_change_within_block().into());
94 }
95
96 validate_usd_currency(call.token)?;
98
99 self.validator_tokens[sender].write(call.token)?;
100
101 self.emit_event(FeeManagerEvent::ValidatorTokenSet(
103 IFeeManager::ValidatorTokenSet {
104 validator: sender,
105 token: call.token,
106 },
107 ))
108 }
109
110 pub fn set_user_token(
117 &mut self,
118 sender: Address,
119 call: IFeeManager::setUserTokenCall,
120 ) -> Result<()> {
121 if !TIP20Factory::new().is_tip20(call.token)? {
123 return Err(FeeManagerError::invalid_token().into());
124 }
125
126 validate_usd_currency(call.token)?;
128
129 if self.storage.spec().is_t3() {
132 let current = self.user_tokens[sender].read()?;
133 if current == call.token {
134 return Ok(());
135 }
136 }
137
138 self.user_tokens[sender].write(call.token)?;
139
140 self.emit_event(FeeManagerEvent::UserTokenSet(IFeeManager::UserTokenSet {
142 user: sender,
143 token: call.token,
144 }))
145 }
146
147 pub fn collect_fee_pre_tx(
158 &mut self,
159 fee_payer: Address,
160 user_token: Address,
161 max_amount: U256,
162 beneficiary: Address,
163 skip_liquidity_check: bool,
164 ) -> Result<Address> {
165 let validator_token = self.get_validator_token(beneficiary)?;
167
168 let mut tip20_token = TIP20Token::from_address(user_token)?;
169
170 tip20_token.ensure_transfer_authorized(fee_payer, self.address)?;
172 tip20_token.transfer_fee_pre_tx(fee_payer, max_amount)?;
173
174 if user_token != validator_token && !skip_liquidity_check {
175 let pool_id = PoolKey::new(user_token, validator_token).get_id();
176 let amount_out_needed = self.check_sufficient_liquidity(pool_id, max_amount)?;
177
178 if self.storage.spec().is_t1c() {
179 self.reserve_pool_liquidity(pool_id, amount_out_needed)?;
180 }
181 }
182
183 Ok(user_token)
185 }
186
187 pub fn collect_fee_post_tx(
197 &mut self,
198 fee_payer: Address,
199 actual_spending: U256,
200 refund_amount: U256,
201 fee_token: Address,
202 beneficiary: Address,
203 ) -> Result<()> {
204 let mut tip20_token = TIP20Token::from_address(fee_token)?;
206 tip20_token.transfer_fee_post_tx(fee_payer, refund_amount, actual_spending)?;
207
208 let validator_token = self.get_validator_token(beneficiary)?;
210
211 if fee_token != validator_token && !actual_spending.is_zero() {
212 self.execute_fee_swap(fee_token, validator_token, actual_spending)?;
214 }
215
216 let amount = if fee_token == validator_token {
217 actual_spending
218 } else {
219 compute_amount_out(actual_spending)?
220 };
221
222 self.increment_collected_fees(beneficiary, validator_token, amount)?;
223
224 Ok(())
225 }
226
227 fn increment_collected_fees(
229 &mut self,
230 validator: Address,
231 token: Address,
232 amount: U256,
233 ) -> Result<()> {
234 if amount.is_zero() {
235 return Ok(());
236 }
237
238 let collected_fees = self.collected_fees[validator][token].read()?;
239 self.collected_fees[validator][token].write(
240 collected_fees
241 .checked_add(amount)
242 .ok_or(TempoPrecompileError::under_overflow())?,
243 )?;
244
245 Ok(())
246 }
247
248 pub fn distribute_fees(&mut self, validator: Address, token: Address) -> Result<()> {
254 let amount = self.collected_fees[validator][token].read()?;
255 if amount.is_zero() {
256 return Ok(());
257 }
258 self.collected_fees[validator][token].write(U256::ZERO)?;
259
260 let mut tip20_token = TIP20Token::from_address(token)?;
262 tip20_token.transfer(
263 self.address,
264 ITIP20::transferCall {
265 to: validator,
266 amount,
267 },
268 )?;
269
270 self.emit_event(FeeManagerEvent::FeesDistributed(
272 IFeeManager::FeesDistributed {
273 validator,
274 token,
275 amount,
276 },
277 ))?;
278
279 Ok(())
280 }
281
282 pub fn user_tokens(&self, call: IFeeManager::userTokensCall) -> Result<Address> {
284 self.user_tokens[call.user].read()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use tempo_chainspec::hardfork::TempoHardfork;
291 use tempo_contracts::precompiles::TIP20Error;
292
293 use super::*;
294 use crate::{
295 TIP_FEE_MANAGER_ADDRESS,
296 error::TempoPrecompileError,
297 storage::{ContractStorage, StorageCtx, hashmap::HashMapStorageProvider},
298 test_util::TIP20Setup,
299 tip20::{ITIP20, TIP20Token},
300 };
301
302 #[test]
303 fn test_set_user_token() -> eyre::Result<()> {
304 let mut storage = HashMapStorageProvider::new(1);
305 let user = Address::random();
306 StorageCtx::enter(&mut storage, || {
307 let token = TIP20Setup::create("Test", "TST", user).apply()?;
308
309 let mut fee_manager = TipFeeManager::new();
312
313 let call = IFeeManager::setUserTokenCall {
314 token: token.address(),
315 };
316 let result = fee_manager.set_user_token(user, call);
317 assert!(result.is_ok());
318
319 let call = IFeeManager::userTokensCall { user };
320 assert_eq!(fee_manager.user_tokens(call)?, token.address());
321
322 Ok(())
323 })
324 }
325
326 #[test]
327 fn test_set_user_token_noop_when_unchanged_pre_t3() -> eyre::Result<()> {
328 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T2);
329 let user = Address::random();
330 StorageCtx::enter(&mut storage, || {
331 let token = TIP20Setup::create("Test", "TST", user).apply()?;
332 let mut fee_manager = TipFeeManager::new();
333
334 let call = IFeeManager::setUserTokenCall {
335 token: token.address(),
336 };
337
338 fee_manager.set_user_token(user, call.clone())?;
339 fee_manager.set_user_token(user, call)?;
340 let event_count = StorageCtx.get_events(TIP_FEE_MANAGER_ADDRESS).len();
341 assert_eq!(
342 event_count, 2,
343 "pre-T3: event emitted even when token unchanged"
344 );
345
346 Ok(())
347 })
348 }
349
350 #[test]
351 fn test_set_user_token_noop_when_unchanged_t3() -> eyre::Result<()> {
352 let mut storage = HashMapStorageProvider::new_with_spec(1, TempoHardfork::T3);
353 let user = Address::random();
354 StorageCtx::enter(&mut storage, || {
355 let token = TIP20Setup::create("Test", "TST", user).apply()?;
356 let mut fee_manager = TipFeeManager::new();
357
358 let call = IFeeManager::setUserTokenCall {
359 token: token.address(),
360 };
361
362 fee_manager.set_user_token(user, call.clone())?;
363 let event_count = StorageCtx.get_events(TIP_FEE_MANAGER_ADDRESS).len();
364 assert_eq!(event_count, 1, "first set_user_token should emit event");
365
366 fee_manager.set_user_token(user, call)?;
367 let event_count = StorageCtx.get_events(TIP_FEE_MANAGER_ADDRESS).len();
368 assert_eq!(
369 event_count, 1,
370 "T3+: repeated set_user_token with same token should not emit event"
371 );
372
373 Ok(())
374 })
375 }
376
377 #[test]
378 fn test_set_validator_token() -> eyre::Result<()> {
379 let mut storage = HashMapStorageProvider::new(1);
380 let validator = Address::random();
381 let admin = Address::random();
382 let beneficiary = Address::random();
383 StorageCtx::enter(&mut storage, || {
384 let token = TIP20Setup::create("Test", "TST", admin).apply()?;
385 let mut fee_manager = TipFeeManager::new();
386
387 let call = IFeeManager::setValidatorTokenCall {
388 token: token.address(),
389 };
390
391 let result = fee_manager.set_validator_token(validator, call.clone(), validator);
393 assert_eq!(
394 result,
395 Err(TempoPrecompileError::FeeManagerError(
396 FeeManagerError::cannot_change_within_block()
397 ))
398 );
399
400 let result = fee_manager.set_validator_token(validator, call, beneficiary);
402 assert!(result.is_ok());
403
404 let returned_token = fee_manager.get_validator_token(validator)?;
405 assert_eq!(returned_token, token.address());
406
407 Ok(())
408 })
409 }
410
411 #[test]
412 fn test_set_validator_token_cannot_change_within_block() -> eyre::Result<()> {
413 let mut storage = HashMapStorageProvider::new(1);
414 let validator = Address::random();
415 let beneficiary = Address::random();
416 let admin = Address::random();
417 StorageCtx::enter(&mut storage, || {
418 let token = TIP20Setup::create("Test", "TST", admin).apply()?;
419 let mut fee_manager = TipFeeManager::new();
420
421 let call = IFeeManager::setValidatorTokenCall {
422 token: token.address(),
423 };
424
425 let result = fee_manager.set_validator_token(validator, call.clone(), beneficiary);
427 assert!(result.is_ok());
428
429 let result = fee_manager.set_validator_token(validator, call, validator);
431 assert_eq!(
432 result,
433 Err(TempoPrecompileError::FeeManagerError(
434 FeeManagerError::cannot_change_within_block()
435 ))
436 );
437
438 Ok(())
439 })
440 }
441
442 #[test]
443 fn test_collect_fee_pre_tx() -> eyre::Result<()> {
444 let mut storage = HashMapStorageProvider::new(1);
445 let user = Address::random();
446 let validator = Address::random();
447 let beneficiary = Address::random();
448 StorageCtx::enter(&mut storage, || {
449 let max_amount = U256::from(10000);
450
451 let token = TIP20Setup::create("Test", "TST", user)
452 .with_issuer(user)
453 .with_mint(user, U256::from(u64::MAX))
454 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
455 .apply()?;
456
457 let mut fee_manager = TipFeeManager::new();
458
459 fee_manager.set_validator_token(
461 validator,
462 IFeeManager::setValidatorTokenCall {
463 token: token.address(),
464 },
465 beneficiary,
466 )?;
467
468 fee_manager.set_user_token(
470 user,
471 IFeeManager::setUserTokenCall {
472 token: token.address(),
473 },
474 )?;
475
476 let result =
478 fee_manager.collect_fee_pre_tx(user, token.address(), max_amount, validator, false);
479 assert!(result.is_ok());
480 assert_eq!(result?, token.address());
481
482 Ok(())
483 })
484 }
485
486 #[test]
487 fn test_collect_fee_post_tx() -> eyre::Result<()> {
488 let mut storage = HashMapStorageProvider::new(1);
489 let user = Address::random();
490 let admin = Address::random();
491 let validator = Address::random();
492 let beneficiary = Address::random();
493 StorageCtx::enter(&mut storage, || {
494 let actual_used = U256::from(6000);
495 let refund_amount = U256::from(4000);
496
497 let token = TIP20Setup::create("Test", "TST", admin)
499 .with_issuer(admin)
500 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(100000000000000_u64))
501 .apply()?;
502
503 let mut fee_manager = TipFeeManager::new();
504
505 fee_manager.set_validator_token(
507 validator,
508 IFeeManager::setValidatorTokenCall {
509 token: token.address(),
510 },
511 beneficiary,
512 )?;
513
514 fee_manager.set_user_token(
516 user,
517 IFeeManager::setUserTokenCall {
518 token: token.address(),
519 },
520 )?;
521
522 let result = fee_manager.collect_fee_post_tx(
524 user,
525 actual_used,
526 refund_amount,
527 token.address(),
528 validator,
529 );
530 assert!(result.is_ok());
531
532 let tracked_amount = fee_manager.collected_fees[validator][token.address()].read()?;
534 assert_eq!(tracked_amount, actual_used);
535
536 let balance = token.balance_of(ITIP20::balanceOfCall { account: user })?;
538 assert_eq!(balance, refund_amount);
539
540 Ok(())
541 })
542 }
543
544 #[test]
545 fn test_rejects_non_usd() -> eyre::Result<()> {
546 let mut storage = HashMapStorageProvider::new(1);
547 let admin = Address::random();
548 let user = Address::random();
549 let validator = Address::random();
550 let beneficiary = Address::random();
551 StorageCtx::enter(&mut storage, || {
552 let non_usd_token = TIP20Setup::create("NonUSD", "EUR", admin)
554 .currency("EUR")
555 .apply()?;
556
557 let mut fee_manager = TipFeeManager::new();
558
559 let call = IFeeManager::setUserTokenCall {
561 token: non_usd_token.address(),
562 };
563 let result = fee_manager.set_user_token(user, call);
564 assert!(matches!(
565 result,
566 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
567 ));
568
569 let call = IFeeManager::setValidatorTokenCall {
571 token: non_usd_token.address(),
572 };
573 let result = fee_manager.set_validator_token(validator, call, beneficiary);
574 assert!(matches!(
575 result,
576 Err(TempoPrecompileError::TIP20(TIP20Error::InvalidCurrency(_)))
577 ));
578
579 Ok(())
580 })
581 }
582
583 #[test]
586 fn test_collect_fee_pre_tx_different_tokens() -> eyre::Result<()> {
587 let mut storage = HashMapStorageProvider::new(1);
588 let admin = Address::random();
589 let user = Address::random();
590 let validator = Address::random();
591
592 StorageCtx::enter(&mut storage, || {
593 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
595 .with_issuer(admin)
596 .with_mint(user, U256::from(10000))
597 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
598 .apply()?;
599
600 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
601 .with_issuer(admin)
602 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(10000))
603 .apply()?;
604
605 let mut fee_manager = TipFeeManager::new();
606
607 let pool_id = fee_manager.pool_id(user_token.address(), validator_token.address());
609 fee_manager.pools[pool_id].write(crate::tip_fee_manager::amm::Pool {
610 reserve_user_token: 10000,
611 reserve_validator_token: 10000,
612 })?;
613
614 fee_manager.set_validator_token(
616 validator,
617 IFeeManager::setValidatorTokenCall {
618 token: validator_token.address(),
619 },
620 Address::random(),
621 )?;
622
623 let max_amount = U256::from(1000);
624
625 fee_manager.collect_fee_pre_tx(
627 user,
628 user_token.address(),
629 max_amount,
630 validator,
631 false,
632 )?;
633
634 let collected =
639 fee_manager.collected_fees[validator][validator_token.address()].read()?;
640 assert_eq!(
641 collected,
642 U256::ZERO,
643 "Different tokens: no fees accumulated in pre_tx (swap happens in post_tx)"
644 );
645
646 let pool = fee_manager.pools[pool_id].read()?;
648 assert_eq!(
649 pool.reserve_user_token, 10000,
650 "Reserves unchanged in pre_tx"
651 );
652 assert_eq!(
653 pool.reserve_validator_token, 10000,
654 "Reserves unchanged in pre_tx"
655 );
656
657 Ok(())
658 })
659 }
660
661 #[test]
662 fn test_collect_fee_post_tx_immediate_swap() -> eyre::Result<()> {
663 let mut storage = HashMapStorageProvider::new(1);
664 let admin = Address::random();
665 let user = Address::random();
666 let validator = Address::random();
667
668 StorageCtx::enter(&mut storage, || {
669 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
670 .with_issuer(admin)
671 .with_mint(user, U256::from(10000))
672 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(10000))
673 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
674 .apply()?;
675
676 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
677 .with_issuer(admin)
678 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(10000))
679 .apply()?;
680
681 let mut fee_manager = TipFeeManager::new();
682
683 let pool_id = fee_manager.pool_id(user_token.address(), validator_token.address());
684 fee_manager.pools[pool_id].write(crate::tip_fee_manager::amm::Pool {
685 reserve_user_token: 10000,
686 reserve_validator_token: 10000,
687 })?;
688
689 fee_manager.set_validator_token(
690 validator,
691 IFeeManager::setValidatorTokenCall {
692 token: validator_token.address(),
693 },
694 Address::random(),
695 )?;
696
697 let max_amount = U256::from(1000);
698 let actual_spending = U256::from(800);
699 let refund_amount = U256::from(200);
700
701 fee_manager.collect_fee_pre_tx(
703 user,
704 user_token.address(),
705 max_amount,
706 validator,
707 false,
708 )?;
709
710 fee_manager.collect_fee_post_tx(
712 user,
713 actual_spending,
714 refund_amount,
715 user_token.address(),
716 validator,
717 )?;
718
719 let expected_fee_amount = (actual_spending * U256::from(9970)) / U256::from(10000);
721 let collected =
722 fee_manager.collected_fees[validator][validator_token.address()].read()?;
723 assert_eq!(collected, expected_fee_amount);
724
725 let pool = fee_manager.pools[pool_id].read()?;
727 assert_eq!(pool.reserve_user_token, 10000 + 800);
728 assert_eq!(pool.reserve_validator_token, 10000 - 797);
729
730 let tip20_token = TIP20Token::from_address(user_token.address())?;
732 let user_balance = tip20_token.balance_of(ITIP20::balanceOfCall { account: user })?;
733 assert_eq!(user_balance, U256::from(10000) - max_amount + refund_amount);
734
735 Ok(())
736 })
737 }
738
739 #[test]
741 fn test_collect_fee_pre_tx_insufficient_liquidity() -> eyre::Result<()> {
742 let mut storage = HashMapStorageProvider::new(1);
743 let admin = Address::random();
744 let user = Address::random();
745 let validator = Address::random();
746
747 StorageCtx::enter(&mut storage, || {
748 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
749 .with_issuer(admin)
750 .with_mint(user, U256::from(10000))
751 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
752 .apply()?;
753
754 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
755 .with_issuer(admin)
756 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(100))
757 .apply()?;
758
759 let mut fee_manager = TipFeeManager::new();
760
761 let pool_id = fee_manager.pool_id(user_token.address(), validator_token.address());
762 fee_manager.pools[pool_id].write(crate::tip_fee_manager::amm::Pool {
764 reserve_user_token: 10000,
765 reserve_validator_token: 100,
766 })?;
767
768 fee_manager.set_validator_token(
769 validator,
770 IFeeManager::setValidatorTokenCall {
771 token: validator_token.address(),
772 },
773 Address::random(),
774 )?;
775
776 let max_amount = U256::from(1000);
779
780 let result = fee_manager.collect_fee_pre_tx(
781 user,
782 user_token.address(),
783 max_amount,
784 validator,
785 false,
786 );
787
788 assert!(result.is_err(), "Should fail with insufficient liquidity");
789
790 Ok(())
791 })
792 }
793
794 #[test]
797 fn test_collect_fee_pre_tx_skip_liquidity_check() -> eyre::Result<()> {
798 let mut storage = HashMapStorageProvider::new(1);
799 let admin = Address::random();
800 let user = Address::random();
801 let validator = Address::random();
802
803 StorageCtx::enter(&mut storage, || {
804 let user_token = TIP20Setup::create("UserToken", "UTK", admin)
805 .with_issuer(admin)
806 .with_mint(user, U256::from(10000))
807 .with_approval(user, TIP_FEE_MANAGER_ADDRESS, U256::MAX)
808 .apply()?;
809
810 let validator_token = TIP20Setup::create("ValidatorToken", "VTK", admin)
811 .with_issuer(admin)
812 .apply()?;
813
814 let mut fee_manager = TipFeeManager::new();
815 fee_manager.set_validator_token(
816 validator,
817 IFeeManager::setValidatorTokenCall {
818 token: validator_token.address(),
819 },
820 Address::random(),
821 )?;
822
823 let result = fee_manager.collect_fee_pre_tx(
825 user,
826 user_token.address(),
827 U256::from(1000),
828 validator,
829 false,
830 );
831 assert!(
832 result.is_err(),
833 "Should fail without liquidity, got: {result:?}"
834 );
835
836 let result = fee_manager.collect_fee_pre_tx(
838 user,
839 user_token.address(),
840 U256::from(1000),
841 validator,
842 true,
843 );
844 assert!(result.is_ok());
845 assert_eq!(result?, user_token.address());
846
847 Ok(())
848 })
849 }
850
851 #[test]
853 fn test_distribute_fees_zero_balance() -> eyre::Result<()> {
854 let mut storage = HashMapStorageProvider::new(1);
855 let admin = Address::random();
856 let validator = Address::random();
857
858 StorageCtx::enter(&mut storage, || {
859 let token = TIP20Setup::create("TestToken", "TEST", admin)
860 .with_issuer(admin)
861 .apply()?;
862
863 let mut fee_manager = TipFeeManager::new();
864
865 fee_manager.set_validator_token(
866 validator,
867 IFeeManager::setValidatorTokenCall {
868 token: token.address(),
869 },
870 Address::random(),
871 )?;
872
873 let collected = fee_manager.collected_fees[validator][token.address()].read()?;
875 assert_eq!(collected, U256::ZERO);
876
877 let result = fee_manager.distribute_fees(validator, token.address());
879 assert!(result.is_ok(), "Should succeed even with zero balance");
880
881 let tip20_token = TIP20Token::from_address(token.address())?;
883 let balance = tip20_token.balance_of(ITIP20::balanceOfCall { account: validator })?;
884 assert_eq!(balance, U256::ZERO);
885
886 Ok(())
887 })
888 }
889
890 #[test]
892 fn test_distribute_fees() -> eyre::Result<()> {
893 let mut storage = HashMapStorageProvider::new(1);
894 let admin = Address::random();
895 let validator = Address::random();
896
897 StorageCtx::enter(&mut storage, || {
898 let token = TIP20Setup::create("TestToken", "TEST", admin)
900 .with_issuer(admin)
901 .with_mint(TIP_FEE_MANAGER_ADDRESS, U256::from(1000))
902 .apply()?;
903
904 let mut fee_manager = TipFeeManager::new();
905
906 fee_manager.set_validator_token(
908 validator,
909 IFeeManager::setValidatorTokenCall {
910 token: token.address(),
911 },
912 Address::random(), )?;
914
915 let fee_amount = U256::from(500);
917 fee_manager.collected_fees[validator][token.address()].write(fee_amount)?;
918
919 let tip20_token = TIP20Token::from_address(token.address())?;
921 let balance_before =
922 tip20_token.balance_of(ITIP20::balanceOfCall { account: validator })?;
923 assert_eq!(balance_before, U256::ZERO);
924
925 let mut fee_manager = TipFeeManager::new();
927 fee_manager.distribute_fees(validator, token.address())?;
928
929 let tip20_token = TIP20Token::from_address(token.address())?;
931 let balance_after =
932 tip20_token.balance_of(ITIP20::balanceOfCall { account: validator })?;
933 assert_eq!(balance_after, fee_amount);
934
935 let fee_manager = TipFeeManager::new();
937 let remaining = fee_manager.collected_fees[validator][token.address()].read()?;
938 assert_eq!(remaining, U256::ZERO);
939
940 Ok(())
941 })
942 }
943
944 #[test]
945 fn test_initialize_sets_storage_state() -> eyre::Result<()> {
946 let mut storage = HashMapStorageProvider::new(1);
947 StorageCtx::enter(&mut storage, || {
948 let mut fee_manager = TipFeeManager::new();
949
950 assert!(!fee_manager.is_initialized()?);
952
953 fee_manager.initialize()?;
955
956 assert!(fee_manager.is_initialized()?);
958
959 let fee_manager2 = TipFeeManager::new();
961 assert!(fee_manager2.is_initialized()?);
962
963 Ok(())
964 })
965 }
966}