@@ -181,10 +181,20 @@ contract LendingBrokerTest is Test {
181181 );
182182 broker = LendingBroker (payable (address (bProxy)));
183183
184- LendingBroker bnbImpl = new LendingBroker (address (moolah), address (bnbRelayer), address (oracle), address ( WBNB));
184+ LendingBroker bnbImpl = new LendingBroker (address (moolah), address (WBNB));
185185 ERC1967Proxy bnbProxy = new ERC1967Proxy (
186186 address (bnbImpl),
187- abi.encodeWithSelector (LendingBroker.initialize.selector , ADMIN, MANAGER, BOT, PAUSER, address (rateCalc), 10 )
187+ abi.encodeWithSelector (
188+ LendingBroker.initialize.selector ,
189+ ADMIN,
190+ MANAGER,
191+ BOT,
192+ PAUSER,
193+ address (rateCalc),
194+ 10 ,
195+ address (bnbRelayer),
196+ address (oracle)
197+ )
188198 );
189199 bnbBroker = LendingBroker (payable (address (bnbProxy)));
190200
@@ -605,15 +615,11 @@ contract LendingBrokerTest is Test {
605615 uint256 actualDebt = BrokerMath.denormalizeBorrowAmount (normalizedBefore, rate);
606616 uint256 outstandingInterest = actualDebt > principalBefore ? actualDebt - principalBefore : 0 ;
607617
618+ // amount > interest => interest fully cleared, remainder moves principal
608619 uint256 convertAmount = 400 ether ;
609- uint256 expectedInterestShare = outstandingInterest == 0
610- ? 0
611- : BrokerMath.mulDivCeiling (outstandingInterest, convertAmount, principalBefore);
612- uint256 expectedNormalizedDelta = BrokerMath.normalizeBorrowAmount (
613- convertAmount + expectedInterestShare,
614- rate,
615- true
616- );
620+ uint256 expectedInterest = outstandingInterest < convertAmount ? outstandingInterest : convertAmount;
621+ uint256 expectedPrincipalMove = convertAmount - expectedInterest;
622+ uint256 expectedNormalizedDelta = BrokerMath.normalizeBorrowAmount (convertAmount, rate, true );
617623 uint256 expectedNormalizedAfter = normalizedBefore > expectedNormalizedDelta
618624 ? normalizedBefore - expectedNormalizedDelta
619625 : 0 ;
@@ -622,12 +628,12 @@ contract LendingBrokerTest is Test {
622628 broker.convertDynamicToFixed (convertAmount, 51 );
623629
624630 (uint256 principalAfter , uint256 normalizedAfter ) = broker.dynamicLoanPositions (borrower);
625- assertEq (principalAfter, principalBefore - convertAmount , "dynamic principal not reduced by amount " );
631+ assertEq (principalAfter, principalBefore - expectedPrincipalMove , "dynamic principal not reduced correctly " );
626632 assertApproxEqAbs (normalizedAfter, expectedNormalizedAfter, 1 , "normalized debt delta mismatch " );
627633
628634 FixedLoanPosition[] memory fixedPositions = broker.userFixedPositions (borrower);
629635 assertEq (fixedPositions.length , 1 , "fixed position not created " );
630- assertEq (fixedPositions[0 ].principal, convertAmount + expectedInterestShare , "converted fixed principal incorrect " );
636+ assertEq (fixedPositions[0 ].principal, convertAmount, "fixed principal should equal amount " );
631637 assertEq (fixedPositions[0 ].interestRepaid, 0 );
632638 assertEq (fixedPositions[0 ].principalRepaid, 0 );
633639 }
@@ -651,26 +657,91 @@ contract LendingBrokerTest is Test {
651657 uint256 rate = rateCalc.accrueRate (address (broker));
652658 uint256 actualDebt = BrokerMath.denormalizeBorrowAmount (normalizedBefore, rate);
653659 uint256 outstandingInterest = actualDebt > principalBefore ? actualDebt - principalBefore : 0 ;
654- uint256 expectedNormalizedDelta = BrokerMath.normalizeBorrowAmount (actualDebt, rate, true );
655660
661+ // pass amount > actualDebt => capped to interest + principal
656662 vm.prank (borrower);
657- broker.convertDynamicToFixed (principalBefore , 52 );
663+ broker.convertDynamicToFixed (actualDebt + 100 ether , 52 );
658664
659665 (uint256 principalAfter , uint256 normalizedAfter ) = broker.dynamicLoanPositions (borrower);
660666 assertApproxEqAbs (principalAfter, 0 , 1 , "dynamic principal should be cleared " );
661667 assertApproxEqAbs (normalizedAfter, 0 , 1 , "dynamic normalized debt should be cleared " );
662668
663669 FixedLoanPosition[] memory fixedPositions = broker.userFixedPositions (borrower);
664670 assertEq (fixedPositions.length , 1 );
665- assertApproxEqAbs (
666- fixedPositions[0 ].principal,
667- principalBefore + outstandingInterest,
668- 1 ,
669- "fixed principal should equal full outstanding debt "
670- );
671+ uint256 expectedFixed = outstandingInterest + principalBefore;
672+ assertApproxEqAbs (fixedPositions[0 ].principal, expectedFixed, 1 , "fixed principal should equal full debt " );
673+ }
674+
675+ function test_convertDynamicToFixed_exactFullAmount () public {
676+ FixedTermAndRate memory term = FixedTermAndRate ({ termId: 53 , duration: 60 days, apr: 105 * 1e25 });
677+ vm.prank (BOT);
678+ broker.updateFixedTermAndRate (term, false );
679+
680+ uint256 borrowAmt = 500 ether ;
681+ vm.prank (borrower);
682+ broker.borrow (borrowAmt);
683+
684+ vm.prank (MANAGER);
685+ rateCalc.setMaxRatePerSecond (address (broker), RATE_SCALE + 5 );
686+ vm.prank (BOT);
687+ rateCalc.setRatePerSecond (address (broker), RATE_SCALE + 3 );
688+ skip (4 days);
671689
672- // sanity: normalized delta consumed the whole normalized debt (allowing rounding wiggle)
673- assertApproxEqAbs (expectedNormalizedDelta, normalizedBefore, 1 , "normalized debt delta rounding " );
690+ (uint256 principalBefore , uint256 normalizedBefore ) = broker.dynamicLoanPositions (borrower);
691+ uint256 rate = rateCalc.accrueRate (address (broker));
692+ uint256 actualDebt = BrokerMath.denormalizeBorrowAmount (normalizedBefore, rate);
693+ uint256 outstandingInterest = actualDebt > principalBefore ? actualDebt - principalBefore : 0 ;
694+
695+ // amount == interest + principal exactly
696+ uint256 convertAmount = outstandingInterest + principalBefore;
697+
698+ vm.prank (borrower);
699+ broker.convertDynamicToFixed (convertAmount, 53 );
700+
701+ (uint256 principalAfter , uint256 normalizedAfter ) = broker.dynamicLoanPositions (borrower);
702+ assertApproxEqAbs (principalAfter, 0 , 1 , "dynamic principal should be cleared " );
703+ assertApproxEqAbs (normalizedAfter, 0 , 1 , "dynamic normalized debt should be cleared " );
704+
705+ FixedLoanPosition[] memory fixedPositions = broker.userFixedPositions (borrower);
706+ assertEq (fixedPositions.length , 1 );
707+ assertEq (fixedPositions[0 ].principal, convertAmount, "fixed principal should equal amount " );
708+ }
709+
710+ function test_convertDynamicToFixed_excessAmountCapped () public {
711+ FixedTermAndRate memory term = FixedTermAndRate ({ termId: 54 , duration: 30 days, apr: 105 * 1e25 });
712+ vm.prank (BOT);
713+ broker.updateFixedTermAndRate (term, false );
714+
715+ uint256 borrowAmt = 600 ether ;
716+ vm.prank (borrower);
717+ broker.borrow (borrowAmt);
718+
719+ vm.prank (MANAGER);
720+ rateCalc.setMaxRatePerSecond (address (broker), RATE_SCALE + 5 );
721+ vm.prank (BOT);
722+ rateCalc.setRatePerSecond (address (broker), RATE_SCALE + 3 );
723+ skip (3 days);
724+
725+ (uint256 principalBefore , uint256 normalizedBefore ) = broker.dynamicLoanPositions (borrower);
726+ uint256 rate = rateCalc.accrueRate (address (broker));
727+ uint256 actualDebt = BrokerMath.denormalizeBorrowAmount (normalizedBefore, rate);
728+ uint256 outstandingInterest = actualDebt > principalBefore ? actualDebt - principalBefore : 0 ;
729+
730+ // amount much larger than actualDebt => should be capped
731+ uint256 convertAmount = actualDebt + 999 ether ;
732+
733+ vm.prank (borrower);
734+ broker.convertDynamicToFixed (convertAmount, 54 );
735+
736+ (uint256 principalAfter , uint256 normalizedAfter ) = broker.dynamicLoanPositions (borrower);
737+ assertApproxEqAbs (principalAfter, 0 , 1 , "dynamic principal should be cleared " );
738+ assertApproxEqAbs (normalizedAfter, 0 , 1 , "dynamic normalized debt should be cleared " );
739+
740+ FixedLoanPosition[] memory fixedPositions = broker.userFixedPositions (borrower);
741+ assertEq (fixedPositions.length , 1 );
742+ uint256 expectedFixed = outstandingInterest + principalBefore;
743+ assertApproxEqAbs (fixedPositions[0 ].principal, expectedFixed, 1 , "fixed principal capped to actual debt " );
744+ assertLe (fixedPositions[0 ].principal, convertAmount, "fixed principal must be <= amount " );
674745 }
675746
676747 // -----------------------------
@@ -1759,7 +1830,7 @@ contract LendingBrokerTest is Test {
17591830
17601831 /// @dev Deploy a fresh broker proxy with RELAYER/ORACLE unset (simulating V1->V2 upgrade)
17611832 function _deployBrokerWithEmptyRelayerOracle () internal returns (LendingBroker) {
1762- LendingBroker bImpl = new LendingBroker (address (moolah));
1833+ LendingBroker bImpl = new LendingBroker (address (moolah), address ( 0 ) );
17631834 // Use 6-param initialize (no relayer/oracle) by encoding only the original params
17641835 // and leaving RELAYER/ORACLE as address(0)
17651836 ERC1967Proxy bProxy = new ERC1967Proxy (
0 commit comments