@@ -27,6 +27,7 @@ const ILoanTokenLogicProxy = artifacts.require("ILoanTokenLogicProxy");
2727const ILoanTokenModules = artifacts . require ( "ILoanTokenModules" ) ;
2828const FeeSharingCollector = artifacts . require ( "FeeSharingCollector" ) ;
2929const FeeSharingCollectorProxy = artifacts . require ( "FeeSharingCollectorProxy" ) ;
30+ const LiquidityPoolV1Converter = artifacts . require ( "LiquidityPoolV1ConverterMockup" ) ;
3031
3132const mutexUtils = require ( "../deployment/helpers/reentrancy/utils" ) ;
3233
@@ -321,10 +322,6 @@ contract("FeeSharingCollector - Token-Specific Withholding", (accounts) => {
321322 "0" ,
322323 "Should not create checkpoint without transfers"
323324 ) ;
324-
325- // The actual zero-value protection is in _addCheckpoint:
326- // if (amount > 0) { _writeTokenCheckpoint(_token, amount); }
327- // This prevents creating a checkpoint when accumulated amount is 0
328325 } ) ;
329326
330327 it ( "should handle RBTC withholding" , async ( ) => {
@@ -454,6 +451,173 @@ contract("FeeSharingCollector - Token-Specific Withholding", (accounts) => {
454451 totalCheckpoints = await feeSharingCollector . totalTokenCheckpoints ( SOVToken . address ) ;
455452 assert . equal ( totalCheckpoints . toString ( ) , "1" , "Should not create new checkpoint" ) ;
456453 } ) ;
454+
455+ it ( "should reset fee interval baseline when token is added to withhold list" , async ( ) => {
456+ const firstAmount = new BN ( 1 ) ;
457+ const secondAmount = new BN ( 2 ) ;
458+
459+ // Initial transfer creates first checkpoint and sets lastFeeWithdrawalTime.
460+ await SOVToken . approve ( feeSharingCollector . address , firstAmount , { from : root } ) ;
461+ await feeSharingCollector . transferTokens ( SOVToken . address , firstAmount , {
462+ from : root ,
463+ } ) ;
464+
465+ let totalCheckpoints = await feeSharingCollector . totalTokenCheckpoints (
466+ SOVToken . address
467+ ) ;
468+ assert . equal ( totalCheckpoints . toString ( ) , "1" , "Initial checkpoint should be created" ) ;
469+
470+ await feeSharingCollector . addProtocolWithholdToken ( SOVToken . address , { from : root } ) ;
471+
472+ const lastFeeWithdrawalTime = await feeSharingCollector . lastFeeWithdrawalTime (
473+ SOVToken . address
474+ ) ;
475+ assert . equal (
476+ lastFeeWithdrawalTime . toString ( ) ,
477+ "0" ,
478+ "lastFeeWithdrawalTime should be reset while token is withheld"
479+ ) ;
480+
481+ // Remove from withhold list before interval passes and transfer again.
482+ await feeSharingCollector . removeProtocolWithholdToken ( SOVToken . address , {
483+ from : root ,
484+ } ) ;
485+ await SOVToken . approve ( feeSharingCollector . address , secondAmount , { from : root } ) ;
486+ await feeSharingCollector . transferTokens ( SOVToken . address , secondAmount , {
487+ from : root ,
488+ } ) ;
489+
490+ // Because the baseline was reset, transfer creates checkpoint immediately.
491+ totalCheckpoints = await feeSharingCollector . totalTokenCheckpoints ( SOVToken . address ) ;
492+ assert . equal (
493+ totalCheckpoints . toString ( ) ,
494+ "2" ,
495+ "Should create checkpoint immediately after token is removed from withhold list"
496+ ) ;
497+
498+ const unprocessedAfter = await feeSharingCollector . unprocessedAmount ( SOVToken . address ) ;
499+ assert . equal ( unprocessedAfter . toString ( ) , "0" , "No unprocessed amount should remain" ) ;
500+ } ) ;
501+
502+ it ( "should withhold RBTC from withdrawFees() when RBTC dummy token is withheld" , async ( ) => {
503+ const feeAmount = new BN ( wei ( "1" , "ether" ) ) ;
504+
505+ await feeSharingCollector . addProtocolWithholdToken ( RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT , {
506+ from : root ,
507+ } ) ;
508+
509+ // Seed protocol fee accounting and token balance so protocol.withdrawFees can transfer WRBTC.
510+ await sovryn . setLendingFeeTokensHeld ( WRBTC . address , feeAmount ) ;
511+ await WRBTC . deposit ( { from : root , value : feeAmount } ) ;
512+ await WRBTC . transfer ( sovryn . address , feeAmount , { from : root } ) ;
513+
514+ const tx = await feeSharingCollector . withdrawFees ( [ WRBTC . address ] , { from : root } ) ;
515+
516+ expectEvent ( tx , "FeeWithdrawnInRBTC" , {
517+ sender : root ,
518+ amount : feeAmount ,
519+ } ) ;
520+ expectEvent ( tx , "ProtocolRevenueAccumulated" , {
521+ token : RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT ,
522+ amount : feeAmount ,
523+ } ) ;
524+
525+ const withheldFees = await feeSharingCollector . getProtocolWithheldFees (
526+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
527+ ) ;
528+ assert . equal (
529+ withheldFees . toString ( ) ,
530+ feeAmount . toString ( ) ,
531+ "RBTC should be accumulated as withheld protocol fees"
532+ ) ;
533+
534+ const totalCheckpoints = await feeSharingCollector . totalTokenCheckpoints (
535+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
536+ ) ;
537+ assert . equal (
538+ totalCheckpoints . toString ( ) ,
539+ "0" ,
540+ "No staker checkpoint should be created"
541+ ) ;
542+
543+ const unprocessed = await feeSharingCollector . unprocessedAmount (
544+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
545+ ) ;
546+ assert . equal (
547+ unprocessed . toString ( ) ,
548+ "0" ,
549+ "No unprocessed amount should be accumulated"
550+ ) ;
551+ } ) ;
552+
553+ it ( "should withhold RBTC from withdrawFeesAMM() when RBTC dummy token is withheld" , async ( ) => {
554+ const feeAmount = new BN ( wei ( "1" , "ether" ) ) ;
555+ const liquidityPoolV1Converter = await LiquidityPoolV1Converter . new (
556+ SOVToken . address ,
557+ SUSD . address
558+ ) ;
559+
560+ await liquidityPoolV1Converter . setTotalFeeMockupValue ( feeAmount . toString ( ) ) ;
561+ await liquidityPoolV1Converter . setFeesController ( feeSharingCollector . address ) ;
562+ await liquidityPoolV1Converter . setWrbtcToken ( WRBTC . address ) ;
563+ await WRBTC . deposit ( { from : root , value : feeAmount } ) ;
564+ await WRBTC . transfer ( liquidityPoolV1Converter . address , feeAmount , { from : root } ) ;
565+
566+ await feeSharingCollector . addWhitelistedConverterAddress (
567+ liquidityPoolV1Converter . address ,
568+ {
569+ from : root ,
570+ }
571+ ) ;
572+ await feeSharingCollector . addProtocolWithholdToken ( RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT , {
573+ from : root ,
574+ } ) ;
575+
576+ const tx = await feeSharingCollector . withdrawFeesAMM (
577+ [ liquidityPoolV1Converter . address ] ,
578+ {
579+ from : root ,
580+ }
581+ ) ;
582+
583+ expectEvent ( tx , "FeeAMMWithdrawn" , {
584+ sender : root ,
585+ converter : liquidityPoolV1Converter . address ,
586+ amount : feeAmount ,
587+ } ) ;
588+
589+ expectEvent ( tx , "ProtocolRevenueAccumulated" , {
590+ token : RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT ,
591+ amount : feeAmount ,
592+ } ) ;
593+
594+ const withheldFees = await feeSharingCollector . getProtocolWithheldFees (
595+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
596+ ) ;
597+ assert . equal (
598+ withheldFees . toString ( ) ,
599+ feeAmount . toString ( ) ,
600+ "AMM RBTC should be accumulated as withheld protocol fees"
601+ ) ;
602+
603+ const totalCheckpoints = await feeSharingCollector . totalTokenCheckpoints (
604+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
605+ ) ;
606+ assert . equal (
607+ totalCheckpoints . toString ( ) ,
608+ "0" ,
609+ "No staker checkpoint should be created"
610+ ) ;
611+
612+ const unprocessed = await feeSharingCollector . unprocessedAmount (
613+ RBTC_DUMMY_ADDRESS_FOR_CHECKPOINT
614+ ) ;
615+ assert . equal (
616+ unprocessed . toString ( ) ,
617+ "0" ,
618+ "No unprocessed amount should be accumulated"
619+ ) ;
620+ } ) ;
457621 } ) ;
458622
459623 describe ( "Withdraw Protocol Withheld Fees" , ( ) => {
@@ -569,17 +733,7 @@ contract("FeeSharingCollector - Token-Specific Withholding", (accounts) => {
569733
570734 describe ( "Gas Optimization - Zero-Value Checkpoint Prevention" , ( ) => {
571735 it ( "verifies _addCheckpoint logic prevents zero-value checkpoints" , async ( ) => {
572- // The zero-value checkpoint prevention is implemented in _addCheckpoint:
573- //
574- // if (protocolWithheldTokensList.contains(_token)) {
575- // protocolWithheldFees[_token] += amount; // No checkpoint
576- // } else {
577- // if (amount > 0) { // Only create if amount > 0
578- // _writeTokenCheckpoint(_token, amount);
579- // }
580- // }
581- //
582- // This prevents creating checkpoints when unprocessedAmount + _amount = 0
736+ //The actual protection comes from the fact that transferTokens and transferRBTC require _amount > 0
583737
584738 // Verify initial state has no checkpoints
585739 await increaseTime ( FEE_WITHDRAWAL_INTERVAL + 1 ) ;
0 commit comments