diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index 649988b19..c34d00c83 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -181,10 +181,11 @@ jobs: lcov --remove lcov_merged.info -o lcov_merged.info 'test/*' 'script/*' - 'src/Interfaces/*' + 'src/Dependencies/Ownable.sol' + 'src/Zappers/Modules/Exchanges/UniswapV3/UniPriceConverter.sol' 'src/NFTMetadata/*' - 'src/Types/*' 'src/MultiTroveGetter.sol' + 'src/HintHelpers.sol' # Send to coveralls - name: Coveralls diff --git a/contracts/script/coverage.sh b/contracts/script/coverage.sh index 073c3d198..a465e49d1 100755 --- a/contracts/script/coverage.sh +++ b/contracts/script/coverage.sh @@ -22,9 +22,10 @@ cp lcov_foundry.info lcov_merged.info lcov --remove lcov_merged.info -o lcov_merged.info \ 'test/*' \ 'script/*' \ - 'src/Interfaces/*' \ + 'src/Dependencies/Ownable.sol' \ + 'src/Zappers/Modules/Exchanges/UniswapV3/UniPriceConverter.sol' \ 'src/NFTMetadata/*' \ - 'src/Types/*' \ - 'src/MultiTroveGetter.sol' + 'src/MultiTroveGetter.sol' \ + 'src/HintHelpers.sol' genhtml lcov_merged.info --output-directory coverage diff --git a/contracts/test/liquidations.t.sol b/contracts/test/liquidations.t.sol index 64e0c2861..32fbf8668 100644 --- a/contracts/test/liquidations.t.sol +++ b/contracts/test/liquidations.t.sol @@ -110,7 +110,12 @@ contract LiquidationsTest is DevTestSetup { collToken.balanceOf(address(collSurplusPool)), collSurplusAmount, 1, - "CollSurplusPoll should have received collateral" + "CollSurplusPool should have received collateral" + ); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" ); vm.startPrank(A); borrowerOperations.claimCollateral(); @@ -194,7 +199,12 @@ contract LiquidationsTest is DevTestSetup { ); // Check there’s no surplus - assertEq(collToken.balanceOf(address(collSurplusPool)), 0, "CollSurplusPoll should be empty"); + assertEq(collToken.balanceOf(address(collSurplusPool)), 0, "CollSurplusPool should be empty"); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" + ); vm.startPrank(A); vm.expectRevert("CollSurplusPool: No collateral available to claim"); @@ -284,7 +294,12 @@ contract LiquidationsTest is DevTestSetup { "B trove coll mismatch" ); - assertEq(collToken.balanceOf(address(collSurplusPool)), 0, "CollSurplusPoll should be empty"); + assertEq(collToken.balanceOf(address(collSurplusPool)), 0, "CollSurplusPool should be empty"); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" + ); } // Offset and Redistribution @@ -393,7 +408,12 @@ contract LiquidationsTest is DevTestSetup { collToken.balanceOf(address(collSurplusPool)), collSurplusAmount, 10, - "CollSurplusPoll should have received collateral" + "CollSurplusPool should have received collateral" + ); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" ); vm.startPrank(A); borrowerOperations.claimCollateral(); diff --git a/contracts/test/liquidationsLST.t.sol b/contracts/test/liquidationsLST.t.sol index 6cfd411b6..1b07c9ee6 100644 --- a/contracts/test/liquidationsLST.t.sol +++ b/contracts/test/liquidationsLST.t.sol @@ -147,7 +147,12 @@ contract LiquidationsLSTTest is DevTestSetup { collToken.balanceOf(address(collSurplusPool)), collSurplusAmount, 10, - "CollSurplusPoll should have received collateral" + "CollSurplusPool should have received collateral" + ); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" ); vm.startPrank(A); borrowerOperations.claimCollateral(); @@ -296,7 +301,12 @@ contract LiquidationsLSTTest is DevTestSetup { collSurplusAmount = finalValues.collToLiquidate - collPenalty; } assertApproxEqAbs( - collToken.balanceOf(address(collSurplusPool)), collSurplusAmount, 1e9, "CollSurplusPoll mismatch" + collToken.balanceOf(address(collSurplusPool)), collSurplusAmount, 1e9, "CollSurplusPool mismatch" + ); + assertEq( + collToken.balanceOf(address(collSurplusPool)), + collSurplusPool.getCollBalance(), + "CollSurplusPool balance and getter should match" ); if (collSurplusAmount > 0) { vm.startPrank(A); diff --git a/contracts/test/multicollateral.t.sol b/contracts/test/multicollateral.t.sol index e5d5b2a8e..6ae092d4c 100644 --- a/contracts/test/multicollateral.t.sol +++ b/contracts/test/multicollateral.t.sol @@ -110,7 +110,7 @@ contract MulticollateralTest is DevTestSetup { } } - function testMultiCollateralDeployment() public view { + function testMultiCollateralDeployment() public { // check deployment assertEq(collateralRegistry.totalCollaterals(), NUM_COLLATERALS, "Wrong number of branches"); for (uint256 c = 0; c < NUM_COLLATERALS; c++) { @@ -121,6 +121,11 @@ contract MulticollateralTest is DevTestSetup { assertEq(address(collateralRegistry.getToken(c)), ZERO_ADDRESS, "Extra collateral token"); assertEq(address(collateralRegistry.getTroveManager(c)), ZERO_ADDRESS, "Extra TroveManager"); } + // reverts for invalid index + vm.expectRevert("Invalid index"); + collateralRegistry.getToken(10); + vm.expectRevert("Invalid index"); + collateralRegistry.getTroveManager(10); } struct TestValues { @@ -161,8 +166,10 @@ contract MulticollateralTest is DevTestSetup { testValues4.troveId = openMulticollateralTroveNoHints100pctWithIndex(3, A, 0, 10e18, 10000e18, 5e16); makeMulticollateralSPDepositAndClaim(3, A, 10000e18); + // let time go by to reduce redemption rate (/16) + vm.warp(block.timestamp + 1 days); + // Check A’s final bal - // TODO: change when we switch to new gas compensation assertEq(boldToken.balanceOf(A), 16000e18, "Wrong Bold balance before redemption"); // initial balances @@ -195,11 +202,27 @@ contract MulticollateralTest is DevTestSetup { testValues3.fee = fee * testValues3.redeemAmount / redeemAmount * DECIMAL_PRECISION / testValues3.price; testValues4.fee = fee * testValues4.redeemAmount / redeemAmount * DECIMAL_PRECISION / testValues4.price; + // Check redemption rate + assertApproxEqAbs( + collateralRegistry.getRedemptionFeeWithDecay(redeemAmount), + redeemAmount * (INITIAL_BASE_RATE / 16 + REDEMPTION_FEE_FLOOR) / DECIMAL_PRECISION, + 1e7, + "Wrong redemption fee with decay" + ); + + uint256 initialBoldSupply = boldToken.totalSupply(); + // A redeems 1.6k redeem(A, redeemAmount); + // Check redemption rate + assertApproxEqAbs( + collateralRegistry.getRedemptionRate(), + INITIAL_BASE_RATE / 16 + REDEMPTION_FEE_FLOOR + redeemAmount * DECIMAL_PRECISION / initialBoldSupply, + 1e5, + "Wrong redemption rate"); + // Check bold balance - // TODO: change when we switch to new gas compensation assertApproxEqAbs(boldToken.balanceOf(A), 14400e18, 10, "Wrong Bold balance after redemption"); // Check collateral balances diff --git a/contracts/test/zapperGasComp.t.sol b/contracts/test/zapperGasComp.t.sol index 04cba1267..0d7d2d2e1 100644 --- a/contracts/test/zapperGasComp.t.sol +++ b/contracts/test/zapperGasComp.t.sol @@ -96,6 +96,71 @@ contract ZapperGasCompTest is DevTestSetup { assertEq(collToken.balanceOf(A), collBalanceBefore - collAmount, "Coll bal mismatch"); } + function testCanOpenTroveWithBatchManager() external { + uint256 collAmount = 10 ether; + uint256 boldAmount = 10000e18; + + uint256 ethBalanceBefore = A.balance; + uint256 collBalanceBefore = collToken.balanceOf(A); + + registerBatchManager(B); + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: collAmount, + boldAmount: boldAmount, + upperHint: 0, + lowerHint: 0, + annualInterestRate: 0, + batchManager: B, + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + uint256 troveId = gasCompZapper.openTroveWithRawETH{value: ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + + assertEq(troveNFT.ownerOf(troveId), A, "Wrong owner"); + assertGt(troveId, 0, "Trove id should be set"); + assertEq(troveManager.getTroveEntireColl(troveId), collAmount, "Coll mismatch"); + assertGt(troveManager.getTroveEntireDebt(troveId), boldAmount, "Debt mismatch"); + assertEq(boldToken.balanceOf(A), boldAmount, "BOLD bal mismatch"); + assertEq(A.balance, ethBalanceBefore - ETH_GAS_COMPENSATION, "ETH bal mismatch"); + assertEq(collToken.balanceOf(A), collBalanceBefore - collAmount, "Coll bal mismatch"); + assertEq(borrowerOperations.interestBatchManagerOf(troveId), B, "Wrong batch manager"); + (,,,,,,,, address tmBatchManagerAddress,) = troveManager.Troves(troveId); + assertEq(tmBatchManagerAddress, B, "Wrong batch manager (TM)"); + } + + function testCanNotOpenTroveWithBatchManagerAndInterest() external { + uint256 collAmount = 10 ether; + uint256 boldAmount = 10000e18; + + registerBatchManager(B); + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: collAmount, + boldAmount: boldAmount, + upperHint: 0, + lowerHint: 0, + annualInterestRate: 5e16, + batchManager: B, + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + vm.expectRevert("GCZ: Cannot choose interest if joining a batch"); + gasCompZapper.openTroveWithRawETH{value: ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + } + function testCanAddColl() external { uint256 collAmount1 = 10 ether; uint256 boldAmount = 10000e18; @@ -164,6 +229,35 @@ contract ZapperGasCompTest is DevTestSetup { assertEq(collToken.balanceOf(A), collBalanceBefore + collAmount2, "Coll bal mismatch"); } + function testCanNotAddReceiverWithoutRemoveManager() external { + uint256 collAmount = 10 ether; + uint256 boldAmount1 = 10000e18; + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: collAmount, + boldAmount: boldAmount1, + upperHint: 0, + lowerHint: 0, + annualInterestRate: MIN_ANNUAL_INTEREST_RATE, + batchManager: address(0), + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + uint256 troveId = gasCompZapper.openTroveWithRawETH{value: ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + + // Try to add a receiver for the zapper without remove manager + vm.startPrank(A); + vm.expectRevert(AddRemoveManagers.EmptyManager.selector); + gasCompZapper.setRemoveManagerWithReceiver(troveId, address(0), B); + vm.stopPrank(); + } + function testCanRepayBold() external { uint256 collAmount = 10 ether; uint256 boldAmount1 = 10000e18; @@ -262,7 +356,6 @@ contract ZapperGasCompTest is DevTestSetup { assertEq(collToken.balanceOf(B), collBalanceBeforeB, "B Coll bal mismatch"); } - // TODO: more adjustment combinations function testCanAdjustTroveWithdrawCollAndBold() external { uint256 collAmount1 = 10 ether; uint256 collAmount2 = 1 ether; @@ -312,6 +405,55 @@ contract ZapperGasCompTest is DevTestSetup { assertEq(collToken.balanceOf(B), collBalanceBeforeB, "B Coll bal mismatch"); } + function testCanAdjustTroveAddCollAndWithdrawBold() external { + uint256 collAmount1 = 10 ether; + uint256 collAmount2 = 1 ether; + uint256 boldAmount1 = 10000e18; + uint256 boldAmount2 = 1000e18; + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: collAmount1, + boldAmount: boldAmount1, + upperHint: 0, + lowerHint: 0, + annualInterestRate: MIN_ANNUAL_INTEREST_RATE, + batchManager: address(0), + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + uint256 troveId = gasCompZapper.openTroveWithRawETH{value: ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + + uint256 boldBalanceBeforeA = boldToken.balanceOf(A); + uint256 collBalanceBeforeA = collToken.balanceOf(A); + uint256 boldBalanceBeforeB = boldToken.balanceOf(B); + uint256 collBalanceBeforeB = collToken.balanceOf(B); + + // Add a remove manager for the zapper + vm.startPrank(A); + gasCompZapper.setRemoveManagerWithReceiver(troveId, B, A); + vm.stopPrank(); + + // Adjust (add coll and withdraw Bold) + vm.startPrank(B); + gasCompZapper.adjustTrove(troveId, collAmount2, true, boldAmount2, true, boldAmount2); + vm.stopPrank(); + + assertEq(troveManager.getTroveEntireColl(troveId), collAmount1 + collAmount2, "Trove coll mismatch"); + assertApproxEqAbs( + troveManager.getTroveEntireDebt(troveId), boldAmount1 + boldAmount2, 2e18, "Trove debt mismatch" + ); + assertEq(boldToken.balanceOf(A), boldBalanceBeforeA + boldAmount2, "A BOLD bal mismatch"); + assertEq(collToken.balanceOf(A), collBalanceBeforeA, "A Coll bal mismatch"); + assertEq(boldToken.balanceOf(B), boldBalanceBeforeB, "B BOLD bal mismatch"); + assertEq(collToken.balanceOf(B), collBalanceBeforeB - collAmount2, "B Coll bal mismatch"); + } + // TODO: more adjustment combinations function testCanAdjustZombieTroveWithdrawCollAndBold() external { uint256 collAmount1 = 10 ether; diff --git a/contracts/test/zapperWETH.t.sol b/contracts/test/zapperWETH.t.sol index 62d548437..baf3ca223 100644 --- a/contracts/test/zapperWETH.t.sol +++ b/contracts/test/zapperWETH.t.sol @@ -87,6 +87,69 @@ contract ZapperWETHTest is DevTestSetup { assertEq(A.balance, ethBalanceBefore - (ethAmount + ETH_GAS_COMPENSATION), "ETH bal mismatch"); } + function testCanOpenTroveWithBatchManager() external { + uint256 ethAmount = 10 ether; + uint256 boldAmount = 10000e18; + + uint256 ethBalanceBefore = A.balance; + + registerBatchManager(B); + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: 0, // not needed + boldAmount: boldAmount, + upperHint: 0, + lowerHint: 0, + annualInterestRate: 0, + batchManager: B, + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + uint256 troveId = wethZapper.openTroveWithRawETH{value: ethAmount + ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + + assertEq(troveNFT.ownerOf(troveId), A, "Wrong owner"); + assertGt(troveId, 0, "Trove id should be set"); + assertEq(troveManager.getTroveEntireColl(troveId), ethAmount, "Coll mismatch"); + assertGt(troveManager.getTroveEntireDebt(troveId), boldAmount, "Debt mismatch"); + assertEq(boldToken.balanceOf(A), boldAmount, "BOLD bal mismatch"); + assertEq(A.balance, ethBalanceBefore - (ethAmount + ETH_GAS_COMPENSATION), "ETH bal mismatch"); + assertEq(borrowerOperations.interestBatchManagerOf(troveId), B, "Wrong batch manager"); + (,,,,,,,, address tmBatchManagerAddress,) = troveManager.Troves(troveId); + assertEq(tmBatchManagerAddress, B, "Wrong batch manager (TM)"); + } + + function testCanNotOpenTroveWithBatchManagerAndInterest() external { + uint256 ethAmount = 10 ether; + uint256 boldAmount = 10000e18; + + registerBatchManager(B); + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: 0, // not needed + boldAmount: boldAmount, + upperHint: 0, + lowerHint: 0, + annualInterestRate: 5e16, + batchManager: B, + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + vm.expectRevert("WZ: Cannot choose interest if joining a batch"); + wethZapper.openTroveWithRawETH{value: ethAmount + ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + } + function testCanAddColl() external { uint256 ethAmount1 = 10 ether; uint256 boldAmount = 10000e18; @@ -155,6 +218,35 @@ contract ZapperWETHTest is DevTestSetup { assertEq(A.balance, ethBalanceBefore + ethAmount2, "ETH bal mismatch"); } + function testCanNotAddReceiverWithoutRemoveManager() external { + uint256 ethAmount = 10 ether; + uint256 boldAmount1 = 10000e18; + + IZapper.OpenTroveParams memory params = IZapper.OpenTroveParams({ + owner: A, + ownerIndex: 0, + collAmount: 0, // not needed + boldAmount: boldAmount1, + upperHint: 0, + lowerHint: 0, + annualInterestRate: MIN_ANNUAL_INTEREST_RATE, + batchManager: address(0), + maxUpfrontFee: 1000e18, + addManager: address(0), + removeManager: address(0), + receiver: address(0) + }); + vm.startPrank(A); + uint256 troveId = wethZapper.openTroveWithRawETH{value: ethAmount + ETH_GAS_COMPENSATION}(params); + vm.stopPrank(); + + // Try to add a receiver for the zapper without remove manager + vm.startPrank(A); + vm.expectRevert(AddRemoveManagers.EmptyManager.selector); + wethZapper.setRemoveManagerWithReceiver(troveId, address(0), B); + vm.stopPrank(); + } + function testCanRepayBold() external { uint256 ethAmount = 10 ether; uint256 boldAmount1 = 10000e18;