@@ -696,6 +696,7 @@ describe('RedemptionVaultWithMToken', function () {
696696 owner,
697697 dataFeed,
698698 redemptionVault,
699+ mFoneToUsdDataFeed,
699700 } = await loadFixture ( defaultDeploy ) ;
700701
701702 await addPaymentTokenTest (
@@ -719,12 +720,14 @@ describe('RedemptionVaultWithMToken', function () {
719720 const mTbillBefore = await mTBILL . balanceOf (
720721 redemptionVaultWithMToken . address ,
721722 ) ;
722- const tokenOutRate = await dataFeed . getDataInBase18 ( ) ;
723+ const mFoneRate = await mFoneToUsdDataFeed . getDataInBase18 ( ) ;
723724
724725 await redemptionVaultWithMToken . checkAndRedeemMToken (
725726 stableCoins . dai . address ,
726727 parseUnits ( '500' , 9 ) ,
727- tokenOutRate ,
728+ parseUnits ( '1000' ) ,
729+ mFoneRate ,
730+ parseUnits ( '500' ) ,
728731 ) ;
729732
730733 const mTbillAfter = await mTBILL . balanceOf (
@@ -741,6 +744,7 @@ describe('RedemptionVaultWithMToken', function () {
741744 owner,
742745 dataFeed,
743746 redemptionVault,
747+ mFoneToUsdDataFeed,
744748 } = await loadFixture ( defaultDeploy ) ;
745749
746750 await addPaymentTokenTest (
@@ -765,12 +769,14 @@ describe('RedemptionVaultWithMToken', function () {
765769 const mTbillBefore = await mTBILL . balanceOf (
766770 redemptionVaultWithMToken . address ,
767771 ) ;
768- const tokenOutRate = await dataFeed . getDataInBase18 ( ) ;
772+ const mFoneRate = await mFoneToUsdDataFeed . getDataInBase18 ( ) ;
769773
770774 await redemptionVaultWithMToken . checkAndRedeemMToken (
771775 stableCoins . dai . address ,
772776 parseUnits ( '1000' , 9 ) ,
773- tokenOutRate ,
777+ parseUnits ( '1000' ) ,
778+ mFoneRate ,
779+ parseUnits ( '1000' ) ,
774780 ) ;
775781
776782 const mTbillAfter = await mTBILL . balanceOf (
@@ -791,6 +797,7 @@ describe('RedemptionVaultWithMToken', function () {
791797 owner,
792798 dataFeed,
793799 redemptionVault,
800+ mFoneToUsdDataFeed,
794801 } = await loadFixture ( defaultDeploy ) ;
795802
796803 await addPaymentTokenTest (
@@ -808,115 +815,18 @@ describe('RedemptionVaultWithMToken', function () {
808815 true ,
809816 ) ;
810817
811- const tokenOutRate = await dataFeed . getDataInBase18 ( ) ;
818+ const mFoneRate = await mFoneToUsdDataFeed . getDataInBase18 ( ) ;
812819
813820 await expect (
814821 redemptionVaultWithMToken . checkAndRedeemMToken (
815822 stableCoins . dai . address ,
816823 parseUnits ( '1000' , 9 ) ,
817- tokenOutRate ,
824+ parseUnits ( '1000' ) ,
825+ mFoneRate ,
826+ parseUnits ( '1000' ) ,
818827 ) ,
819828 ) . to . be . revertedWith ( 'RVMT: insufficient mToken balance' ) ;
820829 } ) ;
821-
822- it ( 'should succeed with truncation-prone rates (ceil rounding)' , async ( ) => {
823- const {
824- redemptionVaultWithMToken,
825- stableCoins,
826- mTBILL,
827- owner,
828- dataFeed,
829- redemptionVault,
830- mockedAggregatorMToken,
831- } = await loadFixture ( defaultDeploy ) ;
832-
833- await addPaymentTokenTest (
834- { vault : redemptionVaultWithMToken , owner } ,
835- stableCoins . dai ,
836- dataFeed . address ,
837- 0 ,
838- true ,
839- ) ;
840- await addPaymentTokenTest (
841- { vault : redemptionVault , owner } ,
842- stableCoins . dai ,
843- dataFeed . address ,
844- 0 ,
845- true ,
846- ) ;
847-
848- // Set mTBILL rate to 3 — causes (amount * 1e18) / 3e18 to have remainder
849- await setRoundData ( { mockedAggregator : mockedAggregatorMToken } , 3 ) ;
850-
851- await mintToken ( mTBILL , redemptionVaultWithMToken , 100_000 ) ;
852- await mintToken ( stableCoins . dai , redemptionVault , 1_000_000 ) ;
853-
854- // Use STABLECOIN_RATE (1e18) — matches what _redeemInstant passes
855- // for stable tokens via _convertUsdToToken, NOT the data feed rate
856- // (which is 1.02e18). The inner vault also uses STABLECOIN_RATE for
857- // stable tokens, so both sides see the same rate and ceil rounding matters.
858- const tokenOutRate = parseUnits ( '1' , 18 ) ;
859-
860- // Without ceil rounding, the inner vault reverts because:
861- // mTokenAAmount = (1000e18 * 1e18) / 3e18 = 333...333 (truncated)
862- // Inner vault: _truncate((333...333 * 3e18) / 1e18, 9) = 999.999999999e18 < 1000e18
863- await redemptionVaultWithMToken . checkAndRedeemMToken (
864- stableCoins . dai . address ,
865- parseUnits ( '1000' , 9 ) ,
866- tokenOutRate ,
867- ) ;
868-
869- const daiAfter = await stableCoins . dai . balanceOf (
870- redemptionVaultWithMToken . address ,
871- ) ;
872- expect ( daiAfter ) . to . be . gte ( parseUnits ( '1000' , 9 ) ) ;
873- } ) ;
874-
875- it ( 'should not over-redeem when division is exact' , async ( ) => {
876- const {
877- redemptionVaultWithMToken,
878- stableCoins,
879- mTBILL,
880- owner,
881- dataFeed,
882- redemptionVault,
883- mockedAggregatorMToken,
884- } = await loadFixture ( defaultDeploy ) ;
885-
886- await addPaymentTokenTest (
887- { vault : redemptionVaultWithMToken , owner } ,
888- stableCoins . dai ,
889- dataFeed . address ,
890- 0 ,
891- true ,
892- ) ;
893- await addPaymentTokenTest (
894- { vault : redemptionVault , owner } ,
895- stableCoins . dai ,
896- dataFeed . address ,
897- 0 ,
898- true ,
899- ) ;
900-
901- // 1000 * 1e18 / 2e18 = 500 exactly, so no +1 should be applied.
902- await setRoundData ( { mockedAggregator : mockedAggregatorMToken } , 2 ) ;
903-
904- // If rounding is exact ceil, 500 mTBILL is enough. With unconditional +1, this reverts.
905- await mintToken ( mTBILL , redemptionVaultWithMToken , 500 ) ;
906- await mintToken ( stableCoins . dai , redemptionVault , 1_000_000 ) ;
907-
908- const tokenOutRate = parseUnits ( '1' , 18 ) ;
909- await redemptionVaultWithMToken . checkAndRedeemMToken (
910- stableCoins . dai . address ,
911- parseUnits ( '1000' , 9 ) ,
912- tokenOutRate ,
913- ) ;
914-
915- const daiAfter = await stableCoins . dai . balanceOf (
916- redemptionVaultWithMToken . address ,
917- ) ;
918- expect ( daiAfter ) . to . be . gte ( parseUnits ( '1000' , 9 ) ) ;
919- } ) ;
920830 } ) ;
921831
922832 describe ( 'redeemInstant()' , ( ) => {
@@ -1098,7 +1008,7 @@ describe('RedemptionVaultWithMToken', function () {
10981008 stableCoins . dai ,
10991009 100 ,
11001010 {
1101- revertMessage : 'ERC20: burn amount exceeds balance' ,
1011+ revertMessage : 'ERC20: transfer amount exceeds balance' ,
11021012 } ,
11031013 ) ;
11041014 } ) ;
@@ -1745,6 +1655,57 @@ describe('RedemptionVaultWithMToken', function () {
17451655 ) ;
17461656 } ) ;
17471657
1658+ it ( 'redeem 100 mFONE with divergent rates (mFONE=$5, mTBILL=$2) => triggers mTBILL redemption' , async ( ) => {
1659+ const {
1660+ owner,
1661+ redemptionVaultWithMToken,
1662+ stableCoins,
1663+ mTBILL,
1664+ mFONE,
1665+ mTokenToUsdDataFeed,
1666+ mFoneToUsdDataFeed,
1667+ dataFeed,
1668+ redemptionVault,
1669+ mockedAggregatorMFone,
1670+ } = await loadFixture ( defaultDeploy ) ;
1671+
1672+ await addPaymentTokenTest (
1673+ { vault : redemptionVaultWithMToken , owner } ,
1674+ stableCoins . dai ,
1675+ dataFeed . address ,
1676+ 0 ,
1677+ true ,
1678+ ) ;
1679+ await addPaymentTokenTest (
1680+ { vault : redemptionVault , owner } ,
1681+ stableCoins . dai ,
1682+ dataFeed . address ,
1683+ 0 ,
1684+ true ,
1685+ ) ;
1686+
1687+ await setRoundData ( { mockedAggregator : mockedAggregatorMFone } , 5 ) ;
1688+
1689+ await mintToken ( mFONE , owner , 100_000 ) ;
1690+ await mintToken ( mTBILL , redemptionVaultWithMToken , 100_000 ) ;
1691+ await mintToken ( stableCoins . dai , redemptionVault , 1_000_000 ) ;
1692+ await approveBase18 ( owner , mFONE , redemptionVaultWithMToken , 100_000 ) ;
1693+
1694+ await redeemInstantWithMTokenTest (
1695+ {
1696+ redemptionVaultWithMToken,
1697+ owner,
1698+ mTBILL,
1699+ mFONE,
1700+ mFoneToUsdDataFeed,
1701+ mTokenToUsdDataFeed,
1702+ useMTokenSleeve : true ,
1703+ } ,
1704+ stableCoins . dai ,
1705+ 100 ,
1706+ ) ;
1707+ } ) ;
1708+
17481709 it ( 'redeem with waived fee' , async ( ) => {
17491710 const {
17501711 owner,
0 commit comments