diff --git a/contracts/test/TroveManagerTest.js b/contracts/test/TroveManagerTest.js index 2a6ab2e3c..5d7a94601 100644 --- a/contracts/test/TroveManagerTest.js +++ b/contracts/test/TroveManagerTest.js @@ -3,6 +3,7 @@ const testHelpers = require("../utils/testHelpers.js"); const { fundAccounts } = require("../utils/fundAccounts.js"); const TroveManagerTester = artifacts.require("./TroveManagerTester.sol"); const BoldToken = artifacts.require("./BoldToken.sol"); +const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); const th = testHelpers.TestHelper; const dec = th.dec; @@ -47,51 +48,14 @@ contract("TroveManager", async (accounts) => { E, ] = accounts; - const [bountyAddress, lpRewardsAddress, multisig] = accounts.slice(997, 1000); - - let priceFeed; - let boldToken; - let sortedTroves; - let troveManager; - let activePool; - let stabilityPool; - let collSurplusPool; - let defaultPool; - let borrowerOperations; - let hintHelpers; - - let contracts; - - const getOpenTroveTotalDebt = async (boldAmount) => - th.getOpenTroveTotalDebt(contracts, boldAmount); - const getOpenTroveBoldAmount = async (totalDebt) => - th.getOpenTroveBoldAmount(contracts, totalDebt); - const getActualDebtFromComposite = async (compositeDebt) => - th.getActualDebtFromComposite(compositeDebt, contracts); - const getNetBorrowingAmount = async (debtWithFee) => - th.getNetBorrowingAmount(contracts, debtWithFee); - const openTrove = async (params) => th.openTrove(contracts, params); - const withdrawBold = async (params) => th.withdrawBold(contracts, params); - - beforeEach(async () => { - contracts = await deploymentHelper.deployLiquityCore(); + async function deployContractsAndFundFixture() { + const contracts = await deploymentHelper.deployLiquityCore(); contracts.troveManager = await TroveManagerTester.new(); contracts.boldToken = await BoldToken.new( contracts.troveManager.address, contracts.stabilityPool.address, - contracts.borrowerOperations.address - ) - - priceFeed = contracts.priceFeedTestnet; - boldToken = contracts.boldToken; - sortedTroves = contracts.sortedTroves; - troveManager = contracts.troveManager; - activePool = contracts.activePool; - stabilityPool = contracts.stabilityPool; - defaultPool = contracts.defaultPool; - collSurplusPool = contracts.collSurplusPool; - borrowerOperations = contracts.borrowerOperations; - hintHelpers = contracts.hintHelpers; + contracts.borrowerOperations.address, + ); await deploymentHelper.connectCoreContracts(contracts); await fundAccounts([ @@ -116,9 +80,28 @@ contract("TroveManager", async (accounts) => { D, E, ], contracts.WETH); - }); + + return { + getOpenTroveBoldAmount: async (totalDebt) => th.getOpenTroveBoldAmount(contracts, totalDebt), + getNetBorrowingAmount: async (debtWithFee) => th.getNetBorrowingAmount(contracts, debtWithFee), + openTrove: async (params) => th.openTrove(contracts, params), + withdrawBold: async (params) => th.withdrawBold(contracts, params), + contracts: { + ...contracts, + priceFeed: contracts.priceFeedTestnet, + }, + }; + } it("liquidate(): closes a Trove that has ICR < MCR", async () => { + const { + getNetBorrowingAmount, + openTrove, + withdrawBold, + contracts, + } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); @@ -155,11 +138,18 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): decreases ActivePool ETH and BoldDebt by correct amounts", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, activePool } = contracts; + // --- SETUP --- - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); - const { collateral: B_collateral, totalDebt: B_totalDebt } = - await openTrove({ ICR: toBN(dec(21, 17)), extraParams: { from: bob } }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(4, 18)), + extraParams: { from: alice }, + }); + const { collateral: B_collateral, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(21, 17)), + extraParams: { from: bob }, + }); // --- TEST --- @@ -176,7 +166,7 @@ contract("TroveManager", async (accounts) => { assert.equal(activePool_RawEther_Before, A_collateral.add(B_collateral)); th.assertIsApproximatelyEqual( activePool_BoldDebt_Before, - A_totalDebt.add(B_totalDebt) + A_totalDebt.add(B_totalDebt), ); // price drops to 1ETH:100Bold, reducing Bob's ICR below MCR @@ -185,7 +175,7 @@ contract("TroveManager", async (accounts) => { // Confirm system is not in Recovery Mode assert.isFalse(await th.checkRecoveryMode(contracts)); - /* close Bob's Trove. Should liquidate his ether and Bold, + /* close Bob's Trove. Should liquidate his ether and Bold, leaving Alice’s ether and Bold debt in the ActivePool. */ await troveManager.liquidate(bob, { from: owner }); @@ -204,11 +194,18 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): increases DefaultPool ETH and Bold debt by correct amounts", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, defaultPool } = contracts; + // --- SETUP --- - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); - const { collateral: B_collateral, totalDebt: B_totalDebt } = - await openTrove({ ICR: toBN(dec(21, 17)), extraParams: { from: bob } }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(4, 18)), + extraParams: { from: alice }, + }); + const { collateral: B_collateral, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(21, 17)), + extraParams: { from: bob }, + }); // --- TEST --- @@ -250,11 +247,18 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): removes the Trove's stake from the total stakes", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager } = contracts; + // --- SETUP --- - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); - const { collateral: B_collateral, totalDebt: B_totalDebt } = - await openTrove({ ICR: toBN(dec(21, 17)), extraParams: { from: bob } }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(4, 18)), + extraParams: { from: alice }, + }); + const { collateral: B_collateral, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(21, 17)), + extraParams: { from: bob }, + }); // --- TEST --- @@ -277,6 +281,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): Removes the correct trove from the TroveOwners array, and moves the last array element to the new empty slot", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + // --- SETUP --- await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: whale } }); @@ -308,10 +315,10 @@ contract("TroveManager", async (accounts) => { const arrayLength_After = await troveManager.getTroveOwnersCount(); assert.equal(arrayLength_After, 5); - /* After Carol is removed from array, the last element (Erin's address) should have been moved to fill + /* After Carol is removed from array, the last element (Erin's address) should have been moved to fill the empty slot left by Carol, and the array length decreased by one. The final TroveOwners array should be: - - [W, A, B, E, D] + + [W, A, B, E, D] Check all remaining troves in the array are in the correct order */ const trove_0 = await troveManager.TroveOwners(0); @@ -342,11 +349,18 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): updates the snapshots of total stakes and total collateral", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager } = contracts; + // --- SETUP --- - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); - const { collateral: B_collateral, totalDebt: B_totalDebt } = - await openTrove({ ICR: toBN(dec(21, 17)), extraParams: { from: bob } }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(4, 18)), + extraParams: { from: alice }, + }); + const { collateral: B_collateral, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(21, 17)), + extraParams: { from: bob }, + }); // --- TEST --- @@ -369,9 +383,9 @@ contract("TroveManager", async (accounts) => { // close Bob's Trove. His ether*0.995 and Bold should be added to the DefaultPool. await troveManager.liquidate(bob, { from: owner }); - /* check snapshots after. Total stakes should be equal to the remaining stake then the system: + /* check snapshots after. Total stakes should be equal to the remaining stake then the system: 10 ether, Alice's stake. - + Total collateral should be equal to Alice's collateral plus her pending ETH reward (Bob’s collaterale*0.995 ether), earned from the liquidation of Bob's Trove */ const totalStakesSnapshot_After = ( @@ -384,21 +398,27 @@ contract("TroveManager", async (accounts) => { assert.equal(totalStakesSnapshot_After, A_collateral); assert.equal( totalCollateralSnapshot_After, - A_collateral.add(th.applyLiquidationFee(B_collateral)) + A_collateral.add(th.applyLiquidationFee(B_collateral)), ); }); it("liquidate(): updates the L_ETH and L_boldDebt reward-per-unit-staked totals", async () => { + const { contracts, openTrove, withdrawBold } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + // --- SETUP --- - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(8, 18)), extraParams: { from: alice } }); - const { collateral: B_collateral, totalDebt: B_totalDebt } = - await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: bob } }); - const { collateral: C_collateral, totalDebt: C_totalDebt } = - await openTrove({ - ICR: toBN(dec(111, 16)), - extraParams: { from: carol }, - }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(8, 18)), + extraParams: { from: alice }, + }); + const { collateral: B_collateral, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(4, 18)), + extraParams: { from: bob }, + }); + const { collateral: C_collateral, totalDebt: C_totalDebt } = await openTrove({ + ICR: toBN(dec(111, 16)), + extraParams: { from: carol }, + }); // --- TEST --- @@ -422,15 +442,15 @@ contract("TroveManager", async (accounts) => { .mul(mv._1e18BN) .div(A_collateral.add(B_collateral)); const L_boldDebt_expected_1 = C_totalDebt.mul(mv._1e18BN).div( - A_collateral.add(B_collateral) + A_collateral.add(B_collateral), ); assert.isAtMost( th.getDifference(L_ETH_AfterCarolLiquidated, L_ETH_expected_1), - 100 + 100, ); assert.isAtMost( th.getDifference(L_boldDebt_AfterCarolLiquidated, L_boldDebt_expected_1), - 100 + 100, ); // Bob now withdraws Bold, bringing his ICR to 1.11 @@ -452,13 +472,13 @@ contract("TroveManager", async (accounts) => { assert.isFalse(await sortedTroves.contains(bob)); /* Alice now has all the active stake. totalStakes in the system is now 10 ether. - + Bob's pending collateral reward and debt reward are applied to his Trove before his liquidation. - His total collateral*0.995 and debt are then added to the DefaultPool. - + His total collateral*0.995 and debt are then added to the DefaultPool. + The system rewards-per-unit-staked should now be: - + L_ETH = (0.995 / 20) + (10.4975*0.995 / 10) = 1.09425125 ETH L_boldDebt = (180 / 20) + (890 / 10) = 98 Bold */ const L_ETH_AfterBobLiquidated = await troveManager.L_ETH(); @@ -467,36 +487,41 @@ contract("TroveManager", async (accounts) => { const L_ETH_expected_2 = L_ETH_expected_1.add( th .applyLiquidationFee( - B_collateral.add(B_collateral.mul(L_ETH_expected_1).div(mv._1e18BN)) + B_collateral.add(B_collateral.mul(L_ETH_expected_1).div(mv._1e18BN)), ) .mul(mv._1e18BN) - .div(A_collateral) + .div(A_collateral), ); const L_boldDebt_expected_2 = L_boldDebt_expected_1.add( B_totalDebt.add(B_increasedTotalDebt) .add(B_collateral.mul(L_boldDebt_expected_1).div(mv._1e18BN)) .mul(mv._1e18BN) - .div(A_collateral) + .div(A_collateral), ); assert.isAtMost( th.getDifference(L_ETH_AfterBobLiquidated, L_ETH_expected_2), - 100 + 100, ); assert.isAtMost( th.getDifference(L_boldDebt_AfterBobLiquidated, L_boldDebt_expected_2), - 100 + 100, ); }); it("liquidate(): Liquidates undercollateralized trove if there are two troves in the system", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + await openTrove({ ICR: toBN(dec(200, 18)), extraParams: { from: bob, value: dec(100, "ether") }, }); // Alice creates a single trove with 0.7 ETH and a debt of 70 Bold, and provides 10 Bold to SP - const { collateral: A_collateral, totalDebt: A_totalDebt } = - await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: alice } }); + const { collateral: A_collateral, totalDebt: A_totalDebt } = await openTrove({ + ICR: toBN(dec(2, 18)), + extraParams: { from: alice }, + }); // Alice proves 10 Bold to SP await stabilityPool.provideToSP(dec(10, 18), { from: alice }); @@ -534,6 +559,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): reverts if trove is non-existent", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: alice } }); await openTrove({ ICR: toBN(dec(21, 17)), extraParams: { from: bob } }); @@ -555,6 +583,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): reverts if trove has been closed", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, sortedTroves, priceFeed } = contracts; + await openTrove({ ICR: toBN(dec(8, 18)), extraParams: { from: alice } }); await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: bob } }); await openTrove({ ICR: toBN(dec(2, 18)), extraParams: { from: carol } }); @@ -586,6 +617,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): does nothing if trove has >= 110% ICR", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, sortedTroves, priceFeed } = contracts; + await openTrove({ ICR: toBN(dec(3, 18)), extraParams: { from: whale } }); await openTrove({ ICR: toBN(dec(3, 18)), extraParams: { from: bob } }); @@ -604,7 +638,7 @@ contract("TroveManager", async (accounts) => { // Attempt to liquidate bob await assertRevert( troveManager.liquidate(bob), - "TroveManager: nothing to liquidate" + "TroveManager: nothing to liquidate", ); // Check bob active, check whale active @@ -619,6 +653,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): Given the same price and no other trove changes, complete Pool offsets restore the TCR to its value prior to the defaulters opening troves", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // Whale provides Bold to SP const spDeposit = toBN(dec(100, 24)); await openTrove({ @@ -684,6 +721,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): Pool offsets increase the TCR", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // Whale provides Bold to SP const spDeposit = toBN(dec(100, 24)); await openTrove({ @@ -750,6 +790,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): a pure redistribution reduces the TCR only as a result of compensation", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(4, 18)), extraParams: { from: whale } }); await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: alice } }); @@ -798,8 +841,7 @@ contract("TroveManager", async (accounts) => { // Check TCR does not decrease with each liquidation const liquidationTx_1 = await troveManager.liquidate(defaulter_1); - const [liquidatedDebt_1, liquidatedColl_1, gasComp_1] = - th.getEmittedLiquidationValues(liquidationTx_1); + const [liquidatedDebt_1, liquidatedColl_1, gasComp_1] = th.getEmittedLiquidationValues(liquidationTx_1); assert.isFalse(await sortedTroves.contains(defaulter_1)); const TCR_1 = await th.getTCR(contracts); @@ -812,8 +854,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue(expectedTCR_1.eq(TCR_1)); const liquidationTx_2 = await troveManager.liquidate(defaulter_2); - const [liquidatedDebt_2, liquidatedColl_2, gasComp_2] = - th.getEmittedLiquidationValues(liquidationTx_2); + const [liquidatedDebt_2, liquidatedColl_2, gasComp_2] = th.getEmittedLiquidationValues(liquidationTx_2); assert.isFalse(await sortedTroves.contains(defaulter_2)); const TCR_2 = await th.getTCR(contracts); @@ -827,8 +868,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue(expectedTCR_2.eq(TCR_2)); const liquidationTx_3 = await troveManager.liquidate(defaulter_3); - const [liquidatedDebt_3, liquidatedColl_3, gasComp_3] = - th.getEmittedLiquidationValues(liquidationTx_3); + const [liquidatedDebt_3, liquidatedColl_3, gasComp_3] = th.getEmittedLiquidationValues(liquidationTx_3); assert.isFalse(await sortedTroves.contains(defaulter_3)); const TCR_3 = await th.getTCR(contracts); @@ -843,8 +883,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue(expectedTCR_3.eq(TCR_3)); const liquidationTx_4 = await troveManager.liquidate(defaulter_4); - const [liquidatedDebt_4, liquidatedColl_4, gasComp_4] = - th.getEmittedLiquidationValues(liquidationTx_4); + const [liquidatedDebt_4, liquidatedColl_4, gasComp_4] = th.getEmittedLiquidationValues(liquidationTx_4); assert.isFalse(await sortedTroves.contains(defaulter_4)); const TCR_4 = await th.getTCR(contracts); @@ -861,6 +900,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): does not affect the SP deposit or ETH gain when called on an SP depositor's address that has no trove", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, boldToken, stabilityPool } = contracts; + await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: whale } }); const spDeposit = toBN(dec(1, 24)); await openTrove({ @@ -877,14 +919,13 @@ contract("TroveManager", async (accounts) => { // Bob sends tokens to Dennis, who has no trove await boldToken.transfer(dennis, spDeposit, { from: bob }); - //Dennis provides Bold to SP + // Dennis provides Bold to SP await stabilityPool.provideToSP(spDeposit, { from: dennis }); // Carol gets liquidated await priceFeed.setPrice(dec(100, 18)); const liquidationTX_C = await troveManager.liquidate(carol); - const [liquidatedDebt, liquidatedColl, gasComp] = - th.getEmittedLiquidationValues(liquidationTX_C); + const [liquidatedDebt, liquidatedColl, gasComp] = th.getEmittedLiquidationValues(liquidationTX_C); assert.isFalse(await sortedTroves.contains(carol)); // Check Dennis' SP deposit has absorbed Carol's debt, and he has received her liquidated ETH @@ -896,11 +937,11 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.isAtMost( th.getDifference(dennis_Deposit_Before, spDeposit.sub(liquidatedDebt)), - 1000000 + 1000000, ); assert.isAtMost( th.getDifference(dennis_ETHGain_Before, liquidatedColl), - 1000 + 1000, ); // Confirm system is not in Recovery Mode @@ -927,6 +968,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): does not liquidate a SP depositor's trove with ICR > 110%, and does not affect their SP deposit or ETH gain", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: whale } }); const spDeposit = toBN(dec(1, 24)); await openTrove({ @@ -940,14 +984,13 @@ contract("TroveManager", async (accounts) => { extraParams: { from: carol }, }); - //Bob provides Bold to SP + // Bob provides Bold to SP await stabilityPool.provideToSP(spDeposit, { from: bob }); // Carol gets liquidated await priceFeed.setPrice(dec(100, 18)); const liquidationTX_C = await troveManager.liquidate(carol); - const [liquidatedDebt, liquidatedColl, gasComp] = - th.getEmittedLiquidationValues(liquidationTX_C); + const [liquidatedDebt, liquidatedColl, gasComp] = th.getEmittedLiquidationValues(liquidationTX_C); assert.isFalse(await sortedTroves.contains(carol)); // price bounces back - Bob's trove is >110% ICR again @@ -964,7 +1007,7 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.isAtMost( th.getDifference(bob_Deposit_Before, spDeposit.sub(liquidatedDebt)), - 1000000 + 1000000, ); assert.isAtMost(th.getDifference(bob_ETHGain_Before, liquidatedColl), 1000); @@ -974,7 +1017,7 @@ contract("TroveManager", async (accounts) => { // Attempt to liquidate Bob await assertRevert( troveManager.liquidate(bob), - "TroveManager: nothing to liquidate" + "TroveManager: nothing to liquidate", ); // Confirm Bob's trove is still active @@ -992,6 +1035,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): liquidates a SP depositor's trove with ICR < 110%, and the liquidation correctly impacts their SP deposit and ETH gain", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + const A_spDeposit = toBN(dec(3, 24)); const B_spDeposit = toBN(dec(1, 24)); await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); @@ -1011,7 +1057,7 @@ contract("TroveManager", async (accounts) => { extraParams: { from: carol }, }); - //Bob provides Bold to SP + // Bob provides Bold to SP await stabilityPool.provideToSP(B_spDeposit, { from: bob }); // Carol gets liquidated @@ -1020,19 +1066,19 @@ contract("TroveManager", async (accounts) => { // Check Bob' SP deposit has absorbed Carol's debt, and he has received her liquidated ETH const bob_Deposit_Before = await stabilityPool.getCompoundedBoldDeposit( - bob + bob, ); const bob_ETHGain_Before = await stabilityPool.getDepositorETHGain(bob); assert.isAtMost( th.getDifference(bob_Deposit_Before, B_spDeposit.sub(C_debt)), - 1000000 + 1000000, ); assert.isAtMost( th.getDifference( bob_ETHGain_Before, - th.applyLiquidationFee(C_collateral) + th.applyLiquidationFee(C_collateral), ), - 1000 + 1000, ); // Alice provides Bold to SP @@ -1068,16 +1114,16 @@ contract("TroveManager", async (accounts) => { assert.isAtMost( th.getDifference( alice_Deposit_After, - A_spDeposit.sub(B_debt.mul(A_spDeposit).div(totalDeposits)) + A_spDeposit.sub(B_debt.mul(A_spDeposit).div(totalDeposits)), ), - 2000000 // TODO: Unclear why the error margin on these two asserts increased. Rewrite test in Solidity + 2000000, // TODO: Unclear why the error margin on these two asserts increased. Rewrite test in Solidity ); assert.isAtMost( th.getDifference( alice_ETHGain_After, - th.applyLiquidationFee(B_collateral).mul(A_spDeposit).div(totalDeposits) + th.applyLiquidationFee(B_collateral).mul(A_spDeposit).div(totalDeposits), ), - 2000000 // // TODO: Unclear why the error margin on these two asserts increased. Rewrite test in Solidity + 2000000, // // TODO: Unclear why the error margin on these two asserts increased. Rewrite test in Solidity ); const bob_Deposit_After = await stabilityPool.getCompoundedBoldDeposit(bob); @@ -1087,10 +1133,10 @@ contract("TroveManager", async (accounts) => { th.getDifference( bob_Deposit_After, bob_Deposit_Before.sub( - B_debt.mul(bob_Deposit_Before).div(totalDeposits) - ) + B_debt.mul(bob_Deposit_Before).div(totalDeposits), + ), ), - 1000000 + 1000000, ); assert.isAtMost( th.getDifference( @@ -1099,14 +1145,17 @@ contract("TroveManager", async (accounts) => { th .applyLiquidationFee(B_collateral) .mul(bob_Deposit_Before) - .div(totalDeposits) - ) + .div(totalDeposits), + ), ), - 1000000 + 1000000, ); }); it("liquidate(): does not alter the liquidated user's token balance", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { activePool, priceFeed, troveManager, sortedTroves, boldToken, defaultPool } = contracts; + await openTrove({ ICR: toBN(dec(10, 18)), extraParams: { from: whale } }); const { boldAmount: A_boldAmount } = await openTrove({ ICR: toBN(dec(2, 18)), @@ -1161,6 +1210,9 @@ contract("TroveManager", async (accounts) => { }); it("liquidate(): liquidates based on entire/collateral debt (including pending rewards), not raw collateral/debt", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(8, 18)), extraBoldAmount: toBN(dec(100, 18)), @@ -1191,7 +1243,7 @@ contract("TroveManager", async (accounts) => { const bob_ICR_Before = await troveManager.getCurrentICR(bob, price); const carol_ICR_Before = await troveManager.getCurrentICR(carol, price); - /* Before liquidation: + /* Before liquidation: Alice ICR: = (2 * 100 / 50) = 400% Bob ICR: (1 * 100 / 90.5) = 110.5% Carol ICR: (1 * 100 / 100 ) = 100% @@ -1216,7 +1268,7 @@ contract("TroveManager", async (accounts) => { const bob_ICR_After = await troveManager.getCurrentICR(bob, price); const carol_ICR_After = await troveManager.getCurrentICR(carol, price); - /* After liquidation: + /* After liquidation: Alice ICR: (10.15 * 100 / 60) = 183.33% Bob ICR:(1.075 * 100 / 98) = 109.69% @@ -1228,7 +1280,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue(bob_ICR_After.lte(mv._MCR)); assert.isTrue(carol_ICR_After.lte(mv._MCR)); - /* Though Bob's true ICR (including pending rewards) is below the MCR, + /* Though Bob's true ICR (including pending rewards) is below the MCR, check that Bob's raw coll and debt has not changed, and that his "raw" ICR is above the MCR */ const bob_Coll = (await troveManager.Troves(bob))[1]; const bob_Debt = (await troveManager.Troves(bob))[0]; @@ -1243,12 +1295,12 @@ contract("TroveManager", async (accounts) => { // Liquidate Alice, Bob, Carol await assertRevert( troveManager.liquidate(alice), - "TroveManager: nothing to liquidate" + "TroveManager: nothing to liquidate", ); await troveManager.liquidate(bob); await troveManager.liquidate(carol); - /* Check Alice stays active, Carol gets liquidated, and Bob gets liquidated + /* Check Alice stays active, Carol gets liquidated, and Bob gets liquidated (because his pending rewards bring his ICR < MCR) */ assert.isTrue(await sortedTroves.contains(alice)); assert.isFalse(await sortedTroves.contains(bob)); @@ -1263,6 +1315,9 @@ contract("TroveManager", async (accounts) => { // --- batchLiquidateTroves() --- it("batchLiquidateTroves(): liquidates based on entire/collateral debt (including pending rewards), not raw collateral/debt", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(400, 16)), extraParams: { from: alice } }); await openTrove({ ICR: toBN(dec(221, 16)), extraParams: { from: bob } }); await openTrove({ ICR: toBN(dec(200, 16)), extraParams: { from: carol } }); @@ -1279,7 +1334,7 @@ contract("TroveManager", async (accounts) => { const bob_ICR_Before = await troveManager.getCurrentICR(bob, price); const carol_ICR_Before = await troveManager.getCurrentICR(carol, price); - /* Before liquidation: + /* Before liquidation: Alice ICR: = (2 * 100 / 100) = 200% Bob ICR: (1 * 100 / 90.5) = 110.5% Carol ICR: (1 * 100 / 100 ) = 100% @@ -1296,7 +1351,7 @@ contract("TroveManager", async (accounts) => { const bob_ICR_After = await troveManager.getCurrentICR(bob, price); const carol_ICR_After = await troveManager.getCurrentICR(carol, price); - /* After liquidation: + /* After liquidation: Alice ICR: (1.0995 * 100 / 60) = 183.25% Bob ICR:(1.0995 * 100 / 100.5) = 109.40% @@ -1324,7 +1379,7 @@ contract("TroveManager", async (accounts) => { // Confirm system is not in Recovery Mode assert.isFalse(await th.checkRecoveryMode(contracts)); - //liquidate A, B, C + // liquidate A, B, C await troveManager.batchLiquidateTroves([alice, bob, carol]); // Check A stays active, B and C get liquidated @@ -1339,6 +1394,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): liquidates troves with ICR < MCR", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); // A, B, C open troves that will remain active when price drops to 100 @@ -1378,7 +1436,7 @@ contract("TroveManager", async (accounts) => { // Confirm system is not in Recovery Mode assert.isFalse(await th.checkRecoveryMode(contracts)); - //Liquidate sequence + // Liquidate sequence await troveManager.batchLiquidateTroves([alice, bob, carol, dennis, erin, flyn, whale]); // check list size reduced to 4 @@ -1397,6 +1455,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): does not affect the liquidated user's token balances", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, boldToken } = contracts; + await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); // D, E, F open troves that will fall below MCR when price drops to 100 @@ -1418,7 +1479,7 @@ contract("TroveManager", async (accounts) => { // Confirm system is not in Recovery Mode assert.isFalse(await th.checkRecoveryMode(contracts)); - //Liquidate sequence + // Liquidate sequence await troveManager.batchLiquidateTroves([dennis, erin, flyn, whale]); // check list size reduced to 1 @@ -1435,13 +1496,16 @@ contract("TroveManager", async (accounts) => { // Check token balances of users whose troves were liquidated, have not changed assert.equal( (await boldToken.balanceOf(dennis)).toString(), - D_balanceBefore + D_balanceBefore, ); assert.equal((await boldToken.balanceOf(erin)).toString(), E_balanceBefore); assert.equal((await boldToken.balanceOf(flyn)).toString(), F_balanceBefore); }); it("batchLiquidateTroves(): A liquidation sequence containing Pool offsets increases the TCR", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // Whale provides 500 Bold to SP await openTrove({ ICR: toBN(dec(100, 18)), @@ -1489,14 +1553,24 @@ contract("TroveManager", async (accounts) => { // Check pool has 500 Bold assert.equal( (await stabilityPool.getTotalBoldDeposits()).toString(), - dec(500, 18) + dec(500, 18), ); // Confirm system is not in Recovery Mode assert.isFalse(await th.checkRecoveryMode(contracts)); // Liquidate troves - await troveManager.batchLiquidateTroves([alice, bob, carol, dennis, defaulter_1, defaulter_2, defaulter_3, defaulter_4, whale]); + await troveManager.batchLiquidateTroves([ + alice, + bob, + carol, + dennis, + defaulter_1, + defaulter_2, + defaulter_3, + defaulter_4, + whale, + ]); // Check pool has been emptied by the liquidations assert.equal((await stabilityPool.getTotalBoldDeposits()).toString(), "0"); @@ -1516,6 +1590,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): A liquidation sequence of pure redistributions decreases the TCR, due to gas compensation, but up to 0.5%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + const { collateral: W_coll, totalDebt: W_debt } = await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale }, @@ -1583,7 +1660,7 @@ contract("TroveManager", async (accounts) => { const TCR_Before = await th.getTCR(contracts); assert.isAtMost( th.getDifference(TCR_Before, totalColl.mul(price).div(totalDebt)), - 1000 + 1000, ); // Check pool is empty before liquidation @@ -1593,7 +1670,17 @@ contract("TroveManager", async (accounts) => { assert.isFalse(await th.checkRecoveryMode(contracts)); // Liquidate - await troveManager.batchLiquidateTroves([alice, bob, carol, dennis, defaulter_1, defaulter_2, defaulter_3, defaulter_4, whale]); + await troveManager.batchLiquidateTroves([ + alice, + bob, + carol, + dennis, + defaulter_1, + defaulter_2, + defaulter_3, + defaulter_4, + whale, + ]); // Check all defaulters have been liquidated assert.isFalse(await sortedTroves.contains(defaulter_1)); @@ -1613,15 +1700,18 @@ contract("TroveManager", async (accounts) => { totalCollNonDefaulters .add(th.applyLiquidationFee(totalCollDefaulters)) .mul(price) - .div(totalDebt) + .div(totalDebt), ), - 1000 + 1000, ); assert.isTrue(TCR_Before.gte(TCR_After)); assert.isTrue(TCR_After.gte(TCR_Before.mul(toBN(995)).div(toBN(1000)))); }); it("batchLiquidateTroves(): Liquidating troves with SP deposits correctly impacts their SP deposit and ETH gain", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // Whale provides 400 Bold to the SP const whaleDeposit = toBN(dec(40000, 18)); await openTrove({ @@ -1666,7 +1756,7 @@ contract("TroveManager", async (accounts) => { const totalDeposits = whaleDeposit.add(A_deposit).add(B_deposit); assert.equal( (await stabilityPool.getTotalBoldDeposits()).toString(), - totalDeposits + totalDeposits, ); // Confirm system is not in Recovery Mode @@ -1691,7 +1781,7 @@ contract("TroveManager", async (accounts) => { Total Bold in Pool: 800 Bold - Then, liquidation hits A,B,C: + Then, liquidation hits A,B,C: Total liquidated debt = 150 + 350 + 150 = 650 Bold Total liquidated ETH = 1.1 + 3.1 + 1.1 = 5.3 ETH @@ -1713,10 +1803,10 @@ contract("TroveManager", async (accounts) => { // Check remaining Bold Deposits and ETH gain, for whale and depositors whose troves were liquidated const whale_Deposit_After = await stabilityPool.getCompoundedBoldDeposit( - whale + whale, ); const alice_Deposit_After = await stabilityPool.getCompoundedBoldDeposit( - alice + alice, ); const bob_Deposit_After = await stabilityPool.getCompoundedBoldDeposit(bob); @@ -1727,23 +1817,23 @@ contract("TroveManager", async (accounts) => { assert.isAtMost( th.getDifference( whale_Deposit_After, - whaleDeposit.sub(liquidatedDebt.mul(whaleDeposit).div(totalDeposits)) + whaleDeposit.sub(liquidatedDebt.mul(whaleDeposit).div(totalDeposits)), ), - 100000 + 100000, ); assert.isAtMost( th.getDifference( alice_Deposit_After, - A_deposit.sub(liquidatedDebt.mul(A_deposit).div(totalDeposits)) + A_deposit.sub(liquidatedDebt.mul(A_deposit).div(totalDeposits)), ), - 100000 + 100000, ); assert.isAtMost( th.getDifference( bob_Deposit_After, - B_deposit.sub(liquidatedDebt.mul(B_deposit).div(totalDeposits)) + B_deposit.sub(liquidatedDebt.mul(B_deposit).div(totalDeposits)), ), - 100000 + 100000, ); assert.isAtMost( @@ -1752,23 +1842,23 @@ contract("TroveManager", async (accounts) => { th .applyLiquidationFee(liquidatedColl) .mul(whaleDeposit) - .div(totalDeposits) + .div(totalDeposits), ), - 100000 + 100000, ); assert.isAtMost( th.getDifference( alice_ETHGain, - th.applyLiquidationFee(liquidatedColl).mul(A_deposit).div(totalDeposits) + th.applyLiquidationFee(liquidatedColl).mul(A_deposit).div(totalDeposits), ), - 100000 + 100000, ); assert.isAtMost( th.getDifference( bob_ETHGain, - th.applyLiquidationFee(liquidatedColl).mul(B_deposit).div(totalDeposits) + th.applyLiquidationFee(liquidatedColl).mul(B_deposit).div(totalDeposits), ), - 100000 + 100000, ); // Check total remaining deposits and ETH gain in Stability Pool @@ -1779,11 +1869,11 @@ contract("TroveManager", async (accounts) => { assert.isAtMost( th.getDifference(total_BoldinSP, totalDeposits.sub(liquidatedDebt)), - 1000 + 1000, ); assert.isAtMost( th.getDifference(total_ETHinSP, th.applyLiquidationFee(liquidatedColl)), - 1000 + 1000, ); }); @@ -1791,6 +1881,9 @@ contract("TroveManager", async (accounts) => { // Can we achieve / test the same thing using another liquidation function? it("batchLiquidateTroves(): liquidates a Trove that a) was skipped in a previous liquidation and b) has pending rewards", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool, defaultPool, borrowerOperations } = contracts; + // A, B, C, D, E open troves await openTrove({ ICR: toBN(dec(300, 16)), extraParams: { from: C } }); await openTrove({ ICR: toBN(dec(364, 16)), extraParams: { from: D } }); @@ -1826,7 +1919,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue(ICR_C.gt(TCR)); // Attempt to liquidate B and C, which skips C in the liquidation since it is immune - const liqTxBC = await troveManager.batchLiquidateTroves([B,C]); + const liqTxBC = await troveManager.batchLiquidateTroves([B, C]); assert.isTrue(liqTxBC.receipt.status); assert.isFalse(await sortedTroves.contains(B)); assert.isTrue(await sortedTroves.contains(C)); @@ -1850,11 +1943,11 @@ contract("TroveManager", async (accounts) => { const defaultPoolBoldDebt = await defaultPool.getBoldDebt(); assert.isTrue(pendingETH_C.lte(defaultPoolETH)); assert.isTrue(pendingBoldDebt_C.lte(defaultPoolBoldDebt)); - //Check only difference is dust + // Check only difference is dust assert.isAtMost(th.getDifference(pendingETH_C, defaultPoolETH), 1000); assert.isAtMost( th.getDifference(pendingBoldDebt_C, defaultPoolBoldDebt), - 1000 + 1000, ); // Confirm system is still in Recovery Mode @@ -1876,6 +1969,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): closes every trove with ICR < MCR in the given array", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -1912,13 +2008,13 @@ contract("TroveManager", async (accounts) => { // Confirm D-E are ICR > 110% assert.isTrue( - (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR), ); assert.isTrue((await troveManager.getCurrentICR(erin, price)).gte(mv._MCR)); // Confirm Whale is ICR >= 110% assert.isTrue( - (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR), ); liquidationArray = [alice, bob, carol, dennis, erin]; @@ -1939,6 +2035,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): does not liquidate troves that are not in the given array", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -1978,7 +2077,7 @@ contract("TroveManager", async (accounts) => { assert.isTrue((await troveManager.getCurrentICR(bob, price)).lt(mv._MCR)); assert.isTrue((await troveManager.getCurrentICR(carol, price)).lt(mv._MCR)); assert.isTrue( - (await troveManager.getCurrentICR(dennis, price)).lt(mv._MCR) + (await troveManager.getCurrentICR(dennis, price)).lt(mv._MCR), ); assert.isTrue((await troveManager.getCurrentICR(erin, price)).lt(mv._MCR)); @@ -2008,6 +2107,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): does not close troves with ICR >= MCR in the given array", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -2044,13 +2146,13 @@ contract("TroveManager", async (accounts) => { // Confirm D-E are ICR >= 110% assert.isTrue( - (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR), ); assert.isTrue((await troveManager.getCurrentICR(erin, price)).gte(mv._MCR)); // Confirm Whale is ICR > 110% assert.isTrue( - (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR), ); liquidationArray = [alice, bob, carol, dennis, erin]; @@ -2071,6 +2173,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): reverts if array is empty", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -2107,12 +2212,15 @@ contract("TroveManager", async (accounts) => { } catch (error) { assert.include( error.message, - "TroveManager: Calldata address array must not be empty" + "TroveManager: Calldata address array must not be empty", ); } }); it("batchLiquidateTroves(): skips if trove is non-existent", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool } = contracts; + // --- SETUP --- const spDeposit = toBN(dec(500000, 18)); await openTrove({ @@ -2158,13 +2266,13 @@ contract("TroveManager", async (accounts) => { // Confirm D-E are ICR > 110% assert.isTrue( - (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR), ); assert.isTrue((await troveManager.getCurrentICR(erin, price)).gte(mv._MCR)); // Confirm Whale is ICR >= 110% assert.isTrue( - (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR), ); // Liquidate - trove C in between the ones to be liquidated! @@ -2189,7 +2297,7 @@ contract("TroveManager", async (accounts) => { // Check Stability pool has only been reduced by A-B th.assertIsApproximatelyEqual( (await stabilityPool.getTotalBoldDeposits()).toString(), - spDeposit.sub(A_debt).sub(B_debt) + spDeposit.sub(A_debt).sub(B_debt), ); // Confirm system is not in Recovery Mode @@ -2197,6 +2305,9 @@ contract("TroveManager", async (accounts) => { }); it("batchLiquidateTroves(): skips if a trove has been closed", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves, stabilityPool, boldToken, borrowerOperations } = contracts; + // --- SETUP --- const spDeposit = toBN(dec(500000, 18)); await openTrove({ @@ -2254,13 +2365,13 @@ contract("TroveManager", async (accounts) => { // Confirm D-E are ICR > 110% assert.isTrue( - (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(dennis, price)).gte(mv._MCR), ); assert.isTrue((await troveManager.getCurrentICR(erin, price)).gte(mv._MCR)); // Confirm Whale is ICR >= 110% assert.isTrue( - (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR) + (await troveManager.getCurrentICR(whale, price)).gte(mv._MCR), ); // Liquidate - trove C in between the ones to be liquidated! @@ -2283,7 +2394,7 @@ contract("TroveManager", async (accounts) => { // Check Stability pool has only been reduced by A-B th.assertIsApproximatelyEqual( (await stabilityPool.getTotalBoldDeposits()).toString(), - spDeposit.sub(A_debt).sub(B_debt) + spDeposit.sub(A_debt).sub(B_debt), ); // Confirm system is not in Recovery Mode @@ -2296,6 +2407,8 @@ contract("TroveManager", async (accounts) => { // Many of these tests rely on specific ICR ordering in the setup, and close fully redeemed. // It may be more efficient to write wholly new redemption tests in Solidity for Foundry. it.skip("getRedemptionHints(): gets the address of the first Trove and the final ICR of the last Trove involved in a redemption", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- const partialRedemptionAmount = toBN(dec(100, 18)); const { collateral: A_coll, totalDebt: A_totalDebt } = await openTrove({ @@ -2320,8 +2433,11 @@ contract("TroveManager", async (accounts) => { // --- TEST --- const redemptionAmount = C_debt.add(B_debt).add(partialRedemptionAmount); - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); assert.equal(firstRedemptionHint, carol); const expectedICR = A_coll.mul(price) @@ -2331,6 +2447,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("getRedemptionHints(): returns 0 as partialRedemptionHintNICR when reaching _maxIterations", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- await openTrove({ ICR: toBN(dec(310, 16)), extraParams: { from: alice } }); await openTrove({ ICR: toBN(dec(290, 16)), extraParams: { from: bob } }); @@ -2346,13 +2464,16 @@ contract("TroveManager", async (accounts) => { const { partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( "210" + _18_zeros, price, - 2 + 2, ); // limit _maxIterations to 2 assert.equal(partialRedemptionHintNICR, "0"); }); it.skip("redeemCollateral(): cancels the provided Bold with debt from Troves with the lowest ICRs and sends an equivalent amount of Ether", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, priceFeed, sortedTroves } = contracts; + // --- SETUP --- const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(310, 16)), @@ -2371,7 +2492,7 @@ contract("TroveManager", async (accounts) => { }); const partialRedemptionAmount = toBN(2); const redemptionAmount = C_netDebt.add(B_netDebt).add( - partialRedemptionAmount + partialRedemptionAmount, ); // start Dennis with a high ICR await openTrove({ @@ -2390,22 +2511,24 @@ contract("TroveManager", async (accounts) => { // --- TEST --- // Find hints for redeeming 20 Bold - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); // We don't need to use getApproxHint for this test, since it's not the subject of this // test case, and the list is very small, so the correct position is quickly found - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Dennis redeems 20 Bold @@ -2421,7 +2544,7 @@ contract("TroveManager", async (accounts) => { { from: dennis, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -2439,7 +2562,7 @@ contract("TroveManager", async (accounts) => { It leaves her with (3) Bold debt + 50 for gas compensation. */ th.assertIsApproximatelyEqual( alice_debt_After, - A_totalDebt.sub(partialRedemptionAmount) + A_totalDebt.sub(partialRedemptionAmount), ); assert.equal(bob_debt_After, "0"); assert.equal(carol_debt_After, "0"); @@ -2448,7 +2571,7 @@ contract("TroveManager", async (accounts) => { const receivedETH = dennis_ETHBalance_After.sub(dennis_ETHBalance_Before); const expectedTotalETHDrawn = redemptionAmount.div(toBN(200)); // convert redemptionAmount Bold to ETH, at ETH:USD price 200 - const expectedReceivedETH = expectedTotalETHDrawn .sub(toBN(ETHFee)); + const expectedReceivedETH = expectedTotalETHDrawn.sub(toBN(ETHFee)); // console.log("*********************************************************************************") // console.log("ETHFee: " + ETHFee) @@ -2466,11 +2589,14 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.equal( dennis_BoldBalance_After, - dennis_BoldBalance_Before.sub(redemptionAmount) + dennis_BoldBalance_Before.sub(redemptionAmount), ); }); it.skip("redeemCollateral(): with invalid first hint, zero address", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, priceFeed, sortedTroves } = contracts; + // --- SETUP --- const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(310, 16)), @@ -2489,7 +2615,7 @@ contract("TroveManager", async (accounts) => { }); const partialRedemptionAmount = toBN(2); const redemptionAmount = C_netDebt.add(B_netDebt).add( - partialRedemptionAmount + partialRedemptionAmount, ); // start Dennis with a high ICR await openTrove({ @@ -2508,22 +2634,24 @@ contract("TroveManager", async (accounts) => { // --- TEST --- // Find hints for redeeming 20 Bold - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); // We don't need to use getApproxHint for this test, since it's not the subject of this // test case, and the list is very small, so the correct position is quickly found - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Dennis redeems 20 Bold @@ -2539,7 +2667,7 @@ contract("TroveManager", async (accounts) => { { from: dennis, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -2557,7 +2685,7 @@ contract("TroveManager", async (accounts) => { It leaves her with (3) Bold debt + 50 for gas compensation. */ th.assertIsApproximatelyEqual( alice_debt_After, - A_totalDebt.sub(partialRedemptionAmount) + A_totalDebt.sub(partialRedemptionAmount), ); assert.equal(bob_debt_After, "0"); assert.equal(carol_debt_After, "0"); @@ -2575,11 +2703,14 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.equal( dennis_BoldBalance_After, - dennis_BoldBalance_Before.sub(redemptionAmount) + dennis_BoldBalance_Before.sub(redemptionAmount), ); }); it.skip("redeemCollateral(): with invalid first hint, non-existent trove", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + // --- SETUP --- const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(310, 16)), @@ -2598,7 +2729,7 @@ contract("TroveManager", async (accounts) => { }); const partialRedemptionAmount = toBN(2); const redemptionAmount = C_netDebt.add(B_netDebt).add( - partialRedemptionAmount + partialRedemptionAmount, ); // start Dennis with a high ICR await openTrove({ @@ -2617,22 +2748,24 @@ contract("TroveManager", async (accounts) => { // --- TEST --- // Find hints for redeeming 20 Bold - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); // We don't need to use getApproxHint for this test, since it's not the subject of this // test case, and the list is very small, so the correct position is quickly found - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Dennis redeems 20 Bold @@ -2648,7 +2781,7 @@ contract("TroveManager", async (accounts) => { { from: dennis, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -2666,7 +2799,7 @@ contract("TroveManager", async (accounts) => { It leaves her with (3) Bold debt + 50 for gas compensation. */ th.assertIsApproximatelyEqual( alice_debt_After, - A_totalDebt.sub(partialRedemptionAmount) + A_totalDebt.sub(partialRedemptionAmount), ); assert.equal(bob_debt_After, "0"); assert.equal(carol_debt_After, "0"); @@ -2684,11 +2817,14 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.equal( dennis_BoldBalance_After, - dennis_BoldBalance_Before.sub(redemptionAmount) + dennis_BoldBalance_Before.sub(redemptionAmount), ); }); it.skip("redeemCollateral(): with invalid first hint, trove below MCR", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, troveManager, sortedTroves } = contracts; + // --- SETUP --- const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(310, 16)), @@ -2707,7 +2843,7 @@ contract("TroveManager", async (accounts) => { }); const partialRedemptionAmount = toBN(2); const redemptionAmount = C_netDebt.add(B_netDebt).add( - partialRedemptionAmount + partialRedemptionAmount, ); // start Dennis with a high ICR await openTrove({ @@ -2731,22 +2867,24 @@ contract("TroveManager", async (accounts) => { // --- TEST --- // Find hints for redeeming 20 Bold - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); // We don't need to use getApproxHint for this test, since it's not the subject of this // test case, and the list is very small, so the correct position is quickly found - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Dennis redeems 20 Bold @@ -2762,7 +2900,7 @@ contract("TroveManager", async (accounts) => { { from: dennis, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -2780,7 +2918,7 @@ contract("TroveManager", async (accounts) => { It leaves her with (3) Bold debt + 50 for gas compensation. */ th.assertIsApproximatelyEqual( alice_debt_After, - A_totalDebt.sub(partialRedemptionAmount) + A_totalDebt.sub(partialRedemptionAmount), ); assert.equal(bob_debt_After, "0"); assert.equal(carol_debt_After, "0"); @@ -2798,11 +2936,13 @@ contract("TroveManager", async (accounts) => { ).toString(); assert.equal( dennis_BoldBalance_After, - dennis_BoldBalance_Before.sub(redemptionAmount) + dennis_BoldBalance_Before.sub(redemptionAmount), ); }); it.skip("redeemCollateral(): ends the redemption sequence when the token redemption request has been filled", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -2846,7 +2986,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Flyn redeems collateral @@ -2858,14 +2998,14 @@ contract("TroveManager", async (accounts) => { 0, 0, th._100pct, - { from: flyn } + { from: flyn }, ); // Check Flyn's redemption has reduced his balance from 100 to (100-60) = 40 Bold const flynBalance = await boldToken.balanceOf(flyn); th.assertIsApproximatelyEqual( flynBalance, - F_boldAmount.sub(redemptionAmount) + F_boldAmount.sub(redemptionAmount), ); // Check debt of Alice, Bob, Carol @@ -2900,6 +3040,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): ends the redemption sequence when max iterations have been reached", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- await openTrove({ ICR: toBN(dec(100, 18)), extraParams: { from: whale } }); @@ -2934,7 +3076,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Flyn redeems collateral with only two iterations @@ -2946,14 +3088,14 @@ contract("TroveManager", async (accounts) => { 0, 2, th._100pct, - { from: flyn } + { from: flyn }, ); // Check Flyn's redemption has reduced his balance from 100 to (100-40) = 60 Bold const flynBalance = (await boldToken.balanceOf(flyn)).toString(); th.assertIsApproximatelyEqual( flynBalance, - F_boldAmount.sub(redemptionAmount) + F_boldAmount.sub(redemptionAmount), ); // Check debt of Alice, Bob, Carol @@ -2975,27 +3117,21 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): performs partial redemption if resultant debt is > minimum net debt", async () => { - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(10000, 18)), - A, - A, - { from: A, value: dec(1000, "ether") } - ); - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(20000, 18)), - B, - B, - { from: B, value: dec(1000, "ether") } - ); - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(30000, 18)), - C, - C, - { from: C, value: dec(1000, "ether") } - ); + const { contracts, getOpenTroveBoldAmount } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, sortedTroves } = contracts; + + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(10000, 18)), A, A, { + from: A, + value: dec(1000, "ether"), + }); + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(20000, 18)), B, B, { + from: B, + value: dec(1000, "ether"), + }); + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(30000, 18)), C, C, { + from: C, + value: dec(1000, "ether"), + }); // A and C send all their tokens to B await boldToken.transfer(B, await boldToken.balanceOf(A), { from: A }); @@ -3006,7 +3142,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Bold redemption is 55000 US @@ -3015,7 +3151,7 @@ contract("TroveManager", async (accounts) => { B, contracts, BoldRedemption, - th._100pct + th._100pct, ); // Check B, C closed and A remains active @@ -3029,27 +3165,21 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): doesn't perform partial redemption if resultant debt would be < minimum net debt", async () => { - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(6000, 18)), - A, - A, - { from: A, value: dec(1000, "ether") } - ); - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(20000, 18)), - B, - B, - { from: B, value: dec(1000, "ether") } - ); - await th.openTroveWrapper(contracts, - th._100pct, - await getOpenTroveBoldAmount(dec(30000, 18)), - C, - C, - { from: C, value: dec(1000, "ether") } - ); + const { contracts, getOpenTroveBoldAmount } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, sortedTroves } = contracts; + + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(6000, 18)), A, A, { + from: A, + value: dec(1000, "ether"), + }); + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(20000, 18)), B, B, { + from: B, + value: dec(1000, "ether"), + }); + await th.openTroveWrapper(contracts, th._100pct, await getOpenTroveBoldAmount(dec(30000, 18)), C, C, { + from: C, + value: dec(1000, "ether"), + }); // A and C send all their tokens to B await boldToken.transfer(B, await boldToken.balanceOf(A), { from: A }); @@ -3060,7 +3190,7 @@ contract("TroveManager", async (accounts) => { // Skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Bold redemption is 55000 Bold @@ -3069,7 +3199,7 @@ contract("TroveManager", async (accounts) => { B, contracts, BoldRedemption, - th._100pct + th._100pct, ); // Check B, C closed and A remains active @@ -3084,6 +3214,9 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): doesnt perform the final partial redemption in the sequence if the hint is out-of-date", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, priceFeed, sortedTroves } = contracts; + // --- SETUP --- const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(363, 16)), @@ -3104,7 +3237,7 @@ contract("TroveManager", async (accounts) => { const partialRedemptionAmount = toBN(2); const fullfilledRedemptionAmount = C_netDebt.add(B_netDebt); const redemptionAmount = fullfilledRedemptionAmount.add( - partialRedemptionAmount + partialRedemptionAmount, ); await openTrove({ @@ -3122,33 +3255,37 @@ contract("TroveManager", async (accounts) => { // --- TEST --- - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(redemptionAmount, price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + redemptionAmount, + price, + 0, + ); - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); const frontRunRedepmtion = toBN(dec(1, 18)); // Oops, another transaction gets in the way { - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(dec(1, 18), price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + dec(1, 18), + price, + 0, + ); - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - dennis, - dennis - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + dennis, + dennis, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Alice redeems 1 Bold from Carol's Trove @@ -3160,7 +3297,7 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: alice } + { from: alice }, ); } @@ -3176,7 +3313,7 @@ contract("TroveManager", async (accounts) => { { from: dennis, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -3207,13 +3344,16 @@ contract("TroveManager", async (accounts) => { th.assertIsApproximatelyEqual( dennis_BoldBalance_After, dennis_BoldBalance_Before.sub( - fullfilledRedemptionAmount.sub(frontRunRedepmtion) - ) + fullfilledRedemptionAmount.sub(frontRunRedepmtion), + ), ); }); // active debt cannot be zero, as there’s a positive min debt enforced, and at least a trove must exist it.skip("redeemCollateral(): can redeem if there is zero active debt but non-zero debt in DefaultPool", async () => { + const { contracts, getOpenTroveBoldAmount, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, priceFeed } = contracts; + // --- SETUP --- const amount = await getOpenTroveBoldAmount(dec(110, 18)); @@ -3239,7 +3379,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); const redemptionTx = await troveManager.redeemCollateral( @@ -3253,7 +3393,7 @@ contract("TroveManager", async (accounts) => { { from: carol, gasPrice: GAS_PRICE, - } + }, ); const ETHFee = th.getEmittedRedemptionValues(redemptionTx)[3]; @@ -3273,18 +3413,19 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): doesn't touch Troves with ICR < 110%", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- const { netDebt: A_debt } = await openTrove({ ICR: toBN(dec(13, 18)), extraParams: { from: alice }, }); - const { boldAmount: B_boldAmount, totalDebt: B_totalDebt } = - await openTrove({ - ICR: toBN(dec(133, 16)), - extraBoldAmount: A_debt, - extraParams: { from: bob }, - }); + const { boldAmount: B_boldAmount, totalDebt: B_totalDebt } = await openTrove({ + ICR: toBN(dec(133, 16)), + extraBoldAmount: A_debt, + extraParams: { from: bob }, + }); await boldToken.transfer(carol, B_boldAmount, { from: bob }); @@ -3297,7 +3438,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); await troveManager.redeemCollateral( @@ -3308,7 +3449,7 @@ contract("TroveManager", async (accounts) => { 0, 0, th._100pct, - { from: carol } + { from: carol }, ); // Alice's Trove was cleared of debt @@ -3321,6 +3462,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): finds the last Trove with ICR == 110% even if there is more than one", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- const amount1 = toBN(dec(100, 18)); const { totalDebt: A_totalDebt } = await openTrove({ @@ -3368,7 +3511,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); const tx = await troveManager.redeemCollateral( @@ -3380,7 +3523,7 @@ contract("TroveManager", async (accounts) => { 0, 0, th._100pct, - { from: dennis } + { from: dennis }, ); const { debt: alice_Debt_After } = await troveManager.Troves(alice); @@ -3397,6 +3540,9 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): reverts when TCR < MCR", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + await openTrove({ ICR: toBN(dec(200, 16)), extraParams: { from: alice } }); await openTrove({ ICR: toBN(dec(200, 16)), extraParams: { from: bob } }); await openTrove({ ICR: toBN(dec(200, 16)), extraParams: { from: carol } }); @@ -3413,16 +3559,18 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); await assertRevert( th.redeemCollateral(carol, contracts, GAS_PRICE, dec(270, 18)), - "TroveManager: Cannot redeem when TCR < MCR" + "TroveManager: Cannot redeem when TCR < MCR", ); }); it.skip("redeemCollateral(): reverts when argument _amount is 0", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); // Alice opens trove and transfers 500Bold to Erin, the would-be redeemer @@ -3441,7 +3589,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Erin attempts to redeem with _amount = 0 @@ -3453,15 +3601,17 @@ contract("TroveManager", async (accounts) => { 0, 0, th._100pct, - { from: erin } + { from: erin }, ); await assertRevert( redemptionTxPromise, - "TroveManager: Amount must be greater than zero" + "TroveManager: Amount must be greater than zero", ); }); it.skip("redeemCollateral(): reverts if max fee > 100%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + await openTrove({ ICR: toBN(dec(400, 16)), extraBoldAmount: dec(10, 18), @@ -3486,7 +3636,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); await assertRevert( @@ -3495,9 +3645,9 @@ contract("TroveManager", async (accounts) => { contracts, dec(10, 18), GAS_PRICE, - dec(2, 18) + dec(2, 18), ), - "Max fee percentage must be between 0.5% and 100%" + "Max fee percentage must be between 0.5% and 100%", ); await assertRevert( th.redeemCollateralAndGetTxObject( @@ -3505,13 +3655,15 @@ contract("TroveManager", async (accounts) => { contracts, dec(10, 18), GAS_PRICE, - "1000000000000000001" + "1000000000000000001", ), - "Max fee percentage must be between 0.5% and 100%" + "Max fee percentage must be between 0.5% and 100%", ); }); it.skip("redeemCollateral(): reverts if max fee < 0.5%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + await openTrove({ ICR: toBN(dec(400, 16)), extraBoldAmount: dec(10, 18), @@ -3536,7 +3688,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); await assertRevert( @@ -3545,9 +3697,9 @@ contract("TroveManager", async (accounts) => { contracts, GAS_PRICE, dec(10, 18), - 0 + 0, ), - "Max fee percentage must be between 0.5% and 100%" + "Max fee percentage must be between 0.5% and 100%", ); await assertRevert( th.redeemCollateralAndGetTxObject( @@ -3555,9 +3707,9 @@ contract("TroveManager", async (accounts) => { contracts, GAS_PRICE, dec(10, 18), - 1 + 1, ), - "Max fee percentage must be between 0.5% and 100%" + "Max fee percentage must be between 0.5% and 100%", ); await assertRevert( th.redeemCollateralAndGetTxObject( @@ -3565,13 +3717,16 @@ contract("TroveManager", async (accounts) => { contracts, GAS_PRICE, dec(10, 18), - "4999999999999999" + "4999999999999999", ), - "Max fee percentage must be between 0.5% and 100%" + "Max fee percentage must be between 0.5% and 100%", ); }); it.skip("redeemCollateral(): reverts if fee exceeds max fee percentage", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { boldToken, troveManager } = contracts; + const { totalDebt: A_totalDebt } = await openTrove({ ICR: toBN(dec(400, 16)), extraBoldAmount: dec(80, 18), @@ -3598,7 +3753,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Bold redemption is 27 USD: a redemption that incurs a fee of 27/(270 * 2) = 5% @@ -3611,9 +3766,9 @@ contract("TroveManager", async (accounts) => { A, contracts, attemptedBoldRedemption, - lessThan5pct + lessThan5pct, ), - "Fee exceeded provided maximum" + "Fee exceeded provided maximum", ); await troveManager.setBaseRate(0); // artificially zero the baseRate @@ -3624,9 +3779,9 @@ contract("TroveManager", async (accounts) => { A, contracts, attemptedBoldRedemption, - dec(1, 16) + dec(1, 16), ), - "Fee exceeded provided maximum" + "Fee exceeded provided maximum", ); await troveManager.setBaseRate(0); @@ -3637,9 +3792,9 @@ contract("TroveManager", async (accounts) => { A, contracts, attemptedBoldRedemption, - dec(3754, 13) + dec(3754, 13), ), - "Fee exceeded provided maximum" + "Fee exceeded provided maximum", ); await troveManager.setBaseRate(0); @@ -3650,13 +3805,16 @@ contract("TroveManager", async (accounts) => { A, contracts, attemptedBoldRedemption, - dec(5, 15) + dec(5, 15), ), - "Fee exceeded provided maximum" + "Fee exceeded provided maximum", ); }); it.skip("redeemCollateral(): doesn't affect the Stability Pool deposits or ETH gain of redeemed-from troves", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, stabilityPool, priceFeed, sortedTroves } = contracts; + await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale } }); // B, C, D, F open trove @@ -3747,7 +3905,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Erin redeems Bold @@ -3794,6 +3952,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): caller can redeem their entire BoldToken balance", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + const { collateral: W_coll, totalDebt: W_totalDebt } = await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale }, @@ -3846,19 +4006,21 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Erin attempts to redeem 400 Bold - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(dec(400, 18), price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + dec(400, 18), + price, + 0, + ); - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - erin, - erin - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + erin, + erin, + ); await troveManager.redeemCollateral( dec(400, 18), @@ -3868,14 +4030,14 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); // Check activePool debt reduced by 400 Bold const activePool_debt_after = await activePool.getBoldDebt(); assert.equal( activePool_debt_before.sub(activePool_debt_after), - dec(400, 18) + dec(400, 18), ); /* Check ActivePool coll reduced by $400 worth of Ether: at ETH:USD price of $200, this should be 2 ETH. @@ -3885,7 +4047,7 @@ contract("TroveManager", async (accounts) => { // console.log(`activePool_coll_after: ${activePool_coll_after}`) assert.equal( activePool_coll_after.toString(), - activePool_coll_before.sub(toBN(dec(2, 18))) + activePool_coll_before.sub(toBN(dec(2, 18))), ); // Check Erin's balance after @@ -3894,6 +4056,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): reverts when requested redemption amount exceeds caller's Bold token balance", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + const { collateral: W_coll, totalDebt: W_totalDebt } = await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale }, @@ -3949,13 +4113,16 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Erin tries to redeem 1000 Bold try { - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(dec(1000, 18), price, 0)); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + dec(1000, 18), + price, + 0, + )); const { 0: upperPartialRedemptionHint_1, @@ -3963,7 +4130,7 @@ contract("TroveManager", async (accounts) => { } = await sortedTroves.findInsertPosition( partialRedemptionHintNICR, erin, - erin + erin, ); const redemptionTx = await troveManager.redeemCollateral( @@ -3974,7 +4141,7 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); assert.isFalse(redemptionTx.receipt.status); @@ -3982,18 +4149,17 @@ contract("TroveManager", async (accounts) => { assert.include(error.message, "revert"); assert.include( error.message, - "Requested redemption amount must be <= user's Bold token balance" + "Requested redemption amount must be <= user's Bold token balance", ); } // Erin tries to redeem 401 Bold try { - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints( - "401000000000000000000", - price, - 0 - )); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + "401000000000000000000", + price, + 0, + )); const { 0: upperPartialRedemptionHint_2, @@ -4001,7 +4167,7 @@ contract("TroveManager", async (accounts) => { } = await sortedTroves.findInsertPosition( partialRedemptionHintNICR, erin, - erin + erin, ); const redemptionTx = await troveManager.redeemCollateral( @@ -4012,25 +4178,24 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); assert.isFalse(redemptionTx.receipt.status); } catch (error) { assert.include(error.message, "revert"); assert.include( error.message, - "Requested redemption amount must be <= user's Bold token balance" + "Requested redemption amount must be <= user's Bold token balance", ); } // Erin tries to redeem 239482309 Bold try { - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints( - "239482309000000000000000000", - price, - 0 - )); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + "239482309000000000000000000", + price, + 0, + )); const { 0: upperPartialRedemptionHint_3, @@ -4038,7 +4203,7 @@ contract("TroveManager", async (accounts) => { } = await sortedTroves.findInsertPosition( partialRedemptionHintNICR, erin, - erin + erin, ); const redemptionTx = await troveManager.redeemCollateral( @@ -4049,29 +4214,28 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); assert.isFalse(redemptionTx.receipt.status); } catch (error) { assert.include(error.message, "revert"); assert.include( error.message, - "Requested redemption amount must be <= user's Bold token balance" + "Requested redemption amount must be <= user's Bold token balance", ); } // Erin tries to redeem 2^256 - 1 Bold const maxBytes32 = toBN( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", ); try { - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints( - "239482309000000000000000000", - price, - 0 - )); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + "239482309000000000000000000", + price, + 0, + )); const { 0: upperPartialRedemptionHint_4, @@ -4079,7 +4243,7 @@ contract("TroveManager", async (accounts) => { } = await sortedTroves.findInsertPosition( partialRedemptionHintNICR, erin, - erin + erin, ); const redemptionTx = await troveManager.redeemCollateral( @@ -4090,19 +4254,22 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); assert.isFalse(redemptionTx.receipt.status); } catch (error) { assert.include(error.message, "revert"); assert.include( error.message, - "Requested redemption amount must be <= user's Bold token balance" + "Requested redemption amount must be <= user's Bold token balance", ); } }); it.skip("redeemCollateral(): value of issued ETH == face value of redeemed Bold (assuming 1 Bold has value of $1)", async () => { + const { openTrove, contracts } = await loadFixture(deployContractsAndFundFixture); + const { boldToken, troveManager, priceFeed, activePool, sortedTroves } = contracts; + const { collateral: W_coll } = await openTrove({ ICR: toBN(dec(20, 18)), extraParams: { from: whale }, @@ -4151,20 +4318,18 @@ contract("TroveManager", async (accounts) => { let partialRedemptionHintNICR; // Erin redeems 120 Bold - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(_120_Bold, price, 0)); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints(_120_Bold, price, 0)); - const { 0: upperPartialRedemptionHint_1, 1: lowerPartialRedemptionHint_1 } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - erin, - erin - ); + const { 0: upperPartialRedemptionHint_1, 1: lowerPartialRedemptionHint_1 } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + erin, + erin, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); const redemption_1 = await troveManager.redeemCollateral( @@ -4175,31 +4340,29 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: erin } + { from: erin }, ); assert.isTrue(redemption_1.receipt.status); - /* 120 Bold redeemed. Expect $120 worth of ETH removed. At ETH:USD price of $200, + /* 120 Bold redeemed. Expect $120 worth of ETH removed. At ETH:USD price of $200, ETH removed = (120/200) = 0.6 ETH Total active ETH = 280 - 0.6 = 279.4 ETH */ const activeETH_1 = await activePool.getETHBalance(); assert.equal( activeETH_1.toString(), - activeETH_0.sub(toBN(_120_Bold).mul(mv._1e18BN).div(price)) + activeETH_0.sub(toBN(_120_Bold).mul(mv._1e18BN).div(price)), ); // Flyn redeems 373 Bold - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(_373_Bold, price, 0)); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints(_373_Bold, price, 0)); - const { 0: upperPartialRedemptionHint_2, 1: lowerPartialRedemptionHint_2 } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - flyn, - flyn - ); + const { 0: upperPartialRedemptionHint_2, 1: lowerPartialRedemptionHint_2 } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + flyn, + flyn, + ); const redemption_2 = await troveManager.redeemCollateral( _373_Bold, @@ -4209,30 +4372,28 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: flyn } + { from: flyn }, ); assert.isTrue(redemption_2.receipt.status); - /* 373 Bold redeemed. Expect $373 worth of ETH removed. At ETH:USD price of $200, + /* 373 Bold redeemed. Expect $373 worth of ETH removed. At ETH:USD price of $200, ETH removed = (373/200) = 1.865 ETH Total active ETH = 279.4 - 1.865 = 277.535 ETH */ const activeETH_2 = await activePool.getETHBalance(); assert.equal( activeETH_2.toString(), - activeETH_1.sub(toBN(_373_Bold).mul(mv._1e18BN).div(price)) + activeETH_1.sub(toBN(_373_Bold).mul(mv._1e18BN).div(price)), ); // Graham redeems 950 Bold - ({ firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(_950_Bold, price, 0)); + ({ firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints(_950_Bold, price, 0)); - const { 0: upperPartialRedemptionHint_3, 1: lowerPartialRedemptionHint_3 } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - graham, - graham - ); + const { 0: upperPartialRedemptionHint_3, 1: lowerPartialRedemptionHint_3 } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + graham, + graham, + ); const redemption_3 = await troveManager.redeemCollateral( _950_Bold, @@ -4242,24 +4403,27 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: graham } + { from: graham }, ); assert.isTrue(redemption_3.receipt.status); - /* 950 Bold redeemed. Expect $950 worth of ETH removed. At ETH:USD price of $200, + /* 950 Bold redeemed. Expect $950 worth of ETH removed. At ETH:USD price of $200, ETH removed = (950/200) = 4.75 ETH Total active ETH = 277.535 - 4.75 = 272.785 ETH */ const activeETH_3 = (await activePool.getETHBalance()).toString(); assert.equal( activeETH_3.toString(), - activeETH_2.sub(toBN(_950_Bold).mul(mv._1e18BN).div(price)) + activeETH_2.sub(toBN(_950_Bold).mul(mv._1e18BN).div(price)), ); }); // it doesn’t make much sense as there’s now min debt enforced and at least one trove must remain active // the only way to test it is before any trove is opened it.skip("redeemCollateral(): reverts if there is zero outstanding system debt", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken } = contracts; + // --- SETUP --- illegally mint Bold to Bob await boldToken.unprotectedMint(bob, dec(100, 18)); @@ -4267,15 +4431,17 @@ contract("TroveManager", async (accounts) => { const price = await priceFeed.getPrice(); - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints(dec(100, 18), price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + dec(100, 18), + price, + 0, + ); - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - bob, - bob - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + bob, + bob, + ); // Bob tries to redeem his illegally obtained Bold try { @@ -4287,12 +4453,12 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: bob } + { from: bob }, ); } catch (error) { assert.include( error.message, - "VM Exception while processing transaction" + "VM Exception while processing transaction", ); } @@ -4300,6 +4466,8 @@ contract("TroveManager", async (accounts) => { }); it.skip("redeemCollateral(): reverts if caller's tries to redeem more than the outstanding system debt", async () => { + const { openTrove } = await loadFixture(deployContractsAndFundFixture); + // --- SETUP --- illegally mint Bold to Bob await boldToken.unprotectedMint(bob, "101000000000000000000"); @@ -4319,24 +4487,26 @@ contract("TroveManager", async (accounts) => { const totalDebt = C_totalDebt.add(D_totalDebt); th.assertIsApproximatelyEqual( (await activePool.getBoldDebt()).toString(), - totalDebt + totalDebt, ); const price = await priceFeed.getPrice(); - const { firstRedemptionHint, partialRedemptionHintNICR } = - await hintHelpers.getRedemptionHints("101000000000000000000", price, 0); + const { firstRedemptionHint, partialRedemptionHintNICR } = await hintHelpers.getRedemptionHints( + "101000000000000000000", + price, + 0, + ); - const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = - await sortedTroves.findInsertPosition( - partialRedemptionHintNICR, - bob, - bob - ); + const { 0: upperPartialRedemptionHint, 1: lowerPartialRedemptionHint } = await sortedTroves.findInsertPosition( + partialRedemptionHintNICR, + bob, + bob, + ); // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // Bob attempts to redeem his ill-gotten 101 Bold, from a system that has 100 Bold outstanding debt @@ -4349,21 +4519,24 @@ contract("TroveManager", async (accounts) => { partialRedemptionHintNICR, 0, th._100pct, - { from: bob } + { from: bob }, ); } catch (error) { assert.include( error.message, - "VM Exception while processing transaction" + "VM Exception while processing transaction", ); } }); it.skip("redeemCollateral(): a redemption sends the ETH remainder (ETHDrawn - gas) to the redeemer", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed, activePool, defaultPool } = contracts; + // time fast-forwards 1 year, and multisig stakes 1 LQTY await th.fastForwardTime( timeValues.SECONDS_IN_ONE_YEAR, - web3.currentProvider + web3.currentProvider, ); const { totalDebt: W_totalDebt } = await openTrove({ @@ -4405,7 +4578,7 @@ contract("TroveManager", async (accounts) => { A, contracts, redemptionAmount, - GAS_PRICE + GAS_PRICE, ); /* @@ -4421,17 +4594,20 @@ contract("TroveManager", async (accounts) => { th.assertIsApproximatelyEqual( A_balanceAfter.sub(A_balanceBefore), ETHDrawn, - 100000 + 100000, ); }); it.skip("redeemCollateral(): a full redemption (leaving trove with 0 debt), closes the trove", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { sortedTroves } = contracts; + // time fast-forwards 1 year, and multisig stakes 1 LQTY await th.fastForwardTime( timeValues.SECONDS_IN_ONE_YEAR, - web3.currentProvider + web3.currentProvider, ); - + const { netDebt: W_netDebt } = await openTrove({ ICR: toBN(dec(20, 18)), extraBoldAmount: dec(10000, 18), @@ -4482,7 +4658,7 @@ contract("TroveManager", async (accounts) => { // time fast-forwards 1 year, and multisig stakes 1 LQTY await th.fastForwardTime( timeValues.SECONDS_IN_ONE_YEAR, - web3.currentProvider + web3.currentProvider, ); const { netDebt: W_netDebt } = await openTrove({ @@ -4573,11 +4749,11 @@ contract("TroveManager", async (accounts) => { // D is not closed, so cannot open trove await assertRevert( - th.openTroveWrapper(contracts,th._100pct, 0, ZERO_ADDRESS, ZERO_ADDRESS, { + th.openTroveWrapper(contracts, th._100pct, 0, ZERO_ADDRESS, ZERO_ADDRESS, { from: D, value: dec(10, 18), }), - "BorrowerOps: Trove is active" + "BorrowerOps: Trove is active", ); return { @@ -4591,6 +4767,9 @@ contract("TroveManager", async (accounts) => { }; it.skip("redeemCollateral(): emits correct debt and coll values in each redeemed trove's TroveUpdated event", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { sortedTroves } = contracts; + const { netDebt: W_netDebt } = await openTrove({ ICR: toBN(dec(20, 18)), extraBoldAmount: dec(10000, 18), @@ -4625,7 +4804,7 @@ contract("TroveManager", async (accounts) => { // skip bootstrapping phase await th.fastForwardTime( timeValues.SECONDS_IN_ONE_WEEK * 2, - web3.currentProvider + web3.currentProvider, ); // whale redeems Bold. Expect this to fully redeem A, B, C, and partially redeem 15 Bold from D. @@ -4634,7 +4813,7 @@ contract("TroveManager", async (accounts) => { contracts, redemptionAmount, GAS_PRICE, - th._100pct + th._100pct, ); // Check A, B, C have been closed @@ -4647,18 +4826,14 @@ contract("TroveManager", async (accounts) => { const troveUpdatedEvents = th.getAllEventsByName( redemptionTx, - "TroveUpdated" + "TroveUpdated", ); // Get each trove's emitted debt and coll - const [A_emittedDebt, A_emittedColl] = - th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, A); - const [B_emittedDebt, B_emittedColl] = - th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, B); - const [C_emittedDebt, C_emittedColl] = - th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, C); - const [D_emittedDebt, D_emittedColl] = - th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, D); + const [A_emittedDebt, A_emittedColl] = th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, A); + const [B_emittedDebt, B_emittedColl] = th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, B); + const [C_emittedDebt, C_emittedColl] = th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, C); + const [D_emittedDebt, D_emittedColl] = th.getDebtAndCollFromTroveUpdatedEvents(troveUpdatedEvents, D); // Expect A, B, C to have 0 emitted debt and coll, since they were closed assert.equal(A_emittedDebt, "0"); @@ -4668,22 +4843,24 @@ contract("TroveManager", async (accounts) => { assert.equal(C_emittedDebt, "0"); assert.equal(C_emittedColl, "0"); - /* Expect D to have lost 15 debt and (at ETH price of 200) 15/200 = 0.075 ETH. + /* Expect D to have lost 15 debt and (at ETH price of 200) 15/200 = 0.075 ETH. So, expect remaining debt = (85 - 15) = 70, and remaining ETH = 1 - 15/200 = 0.925 remaining. */ const price = await priceFeed.getPrice(); th.assertIsApproximatelyEqual( D_emittedDebt, - D_totalDebt.sub(partialAmount) + D_totalDebt.sub(partialAmount), ); th.assertIsApproximatelyEqual( D_emittedColl, - D_coll.sub(partialAmount.mul(mv._1e18BN).div(price)) + D_coll.sub(partialAmount.mul(mv._1e18BN).div(price)), ); }); it.skip("redeemCollateral(): a redemption that closes a trove leaves the trove's ETH surplus (collateral - ETH drawn) available for the trove owner to claim", async () => { - const { A_netDebt, A_coll, B_netDebt, B_coll, C_netDebt, C_coll } = - await redeemCollateral3Full1Partial(); + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { collSurplusPool } = contracts; + + const { A_netDebt, A_coll, B_netDebt, B_coll, C_netDebt, C_coll } = await redeemCollateral3Full1Partial(); const A_balanceBefore = toBN(await contracts.WETH.balanceOf(A)); const B_balanceBefore = toBN(await contracts.WETH.balanceOf(B)); @@ -4692,7 +4869,7 @@ contract("TroveManager", async (accounts) => { // CollSurplusPool endpoint cannot be called directly await assertRevert( collSurplusPool.claimColl(A), - "CollSurplusPool: Caller is not Borrower Operations" + "CollSurplusPool: Caller is not Borrower Operations", ); const A_balanceAfter = toBN(await contracts.WETH.balanceOf(A)); @@ -4703,19 +4880,22 @@ contract("TroveManager", async (accounts) => { th.assertIsApproximatelyEqual( A_balanceAfter, - A_balanceBefore.add(A_coll.sub(A_netDebt.mul(mv._1e18BN).div(price))) + A_balanceBefore.add(A_coll.sub(A_netDebt.mul(mv._1e18BN).div(price))), ); th.assertIsApproximatelyEqual( B_balanceAfter, - B_balanceBefore.add(B_coll.sub(B_netDebt.mul(mv._1e18BN).div(price))) + B_balanceBefore.add(B_coll.sub(B_netDebt.mul(mv._1e18BN).div(price))), ); th.assertIsApproximatelyEqual( C_balanceAfter, - C_balanceBefore.add(C_coll.sub(C_netDebt.mul(mv._1e18BN).div(price))) + C_balanceBefore.add(C_coll.sub(C_netDebt.mul(mv._1e18BN).div(price))), ); }); it.skip("redeemCollateral(): a redemption that closes a trove leaves the trove's ETH surplus (collateral - ETH drawn) available for the trove owner after re-opening trove", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + const { A_netDebt, A_coll: A_collBefore, @@ -4764,19 +4944,22 @@ contract("TroveManager", async (accounts) => { th.assertIsApproximatelyEqual( A_balanceAfter, - A_balanceBefore.add(A_surplus) + A_balanceBefore.add(A_surplus), ); th.assertIsApproximatelyEqual( B_balanceAfter, - B_balanceBefore.add(B_surplus) + B_balanceBefore.add(B_surplus), ); th.assertIsApproximatelyEqual( C_balanceAfter, - C_balanceBefore.add(C_surplus) + C_balanceBefore.add(C_surplus), ); }); it("getPendingBoldDebtReward(): Returns 0 if there is no pending BoldDebt reward", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, stabilityPool, priceFeed, sortedTroves } = contracts; + // Make some troves const { totalDebt } = await openTrove({ ICR: toBN(dec(2, 18)), @@ -4814,12 +4997,14 @@ contract("TroveManager", async (accounts) => { )[1]; assert.equal(carolSnapshot_L_boldDebt, 0); - const carol_PendingBoldDebtReward = - await troveManager.getPendingBoldDebtReward(carol); + const carol_PendingBoldDebtReward = await troveManager.getPendingBoldDebtReward(carol); assert.equal(carol_PendingBoldDebtReward, 0); }); it("getPendingETHReward(): Returns 0 if there is no pending ETH reward", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, stabilityPool, priceFeed, sortedTroves } = contracts; + // make some troves const { totalDebt } = await openTrove({ ICR: toBN(dec(2, 18)), @@ -4856,7 +5041,7 @@ contract("TroveManager", async (accounts) => { assert.equal(carolSnapshot_L_ETH, 0); const carol_PendingETHReward = await troveManager.getPendingETHReward( - carol + carol, ); assert.equal(carol_PendingETHReward, 0); }); @@ -4864,6 +5049,9 @@ contract("TroveManager", async (accounts) => { // --- computeICR --- it("computeICR(): Returns 0 if trove's coll is worth 0", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = 0; const coll = dec(1, "ether"); const debt = dec(100, 18); @@ -4874,6 +5062,9 @@ contract("TroveManager", async (accounts) => { }); it("computeICR(): Returns 2^256-1 for ETH:USD = 100, coll = 1 ETH, debt = 100 Bold", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = dec(100, 18); const coll = dec(1, "ether"); const debt = dec(100, 18); @@ -4884,6 +5075,9 @@ contract("TroveManager", async (accounts) => { }); it("computeICR(): returns correct ICR for ETH:USD = 100, coll = 200 ETH, debt = 30 Bold", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = dec(100, 18); const coll = dec(200, "ether"); const debt = dec(30, 18); @@ -4894,6 +5088,9 @@ contract("TroveManager", async (accounts) => { }); it("computeICR(): returns correct ICR for ETH:USD = 250, coll = 1350 ETH, debt = 127 Bold", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = "250000000000000000000"; const coll = "1350000000000000000000"; const debt = "127000000000000000000"; @@ -4904,6 +5101,9 @@ contract("TroveManager", async (accounts) => { }); it("computeICR(): returns correct ICR for ETH:USD = 100, coll = 1 ETH, debt = 54321 Bold", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = dec(100, 18); const coll = dec(1, "ether"); const debt = "54321000000000000000000"; @@ -4914,23 +5114,28 @@ contract("TroveManager", async (accounts) => { }); it("computeICR(): Returns 2^256-1 if trove has non-zero coll and zero debt", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const price = dec(100, 18); const coll = dec(1, "ether"); const debt = 0; const ICR = web3.utils.toHex( - await troveManager.computeICR(coll, debt, price) + await troveManager.computeICR(coll, debt, price), ); - const maxBytes32 = - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + const maxBytes32 = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; assert.equal(ICR, maxBytes32); }); // --- checkRecoveryMode --- - //TCR < 150% + // TCR < 150% it("checkRecoveryMode(): Returns true when TCR < 150%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + await priceFeed.setPrice(dec(100, 18)); await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: alice } }); @@ -4947,6 +5152,9 @@ contract("TroveManager", async (accounts) => { // TCR == 150% it("checkRecoveryMode(): Returns false when TCR == 150%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + await priceFeed.setPrice(dec(100, 18)); await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: alice } }); @@ -4961,6 +5169,9 @@ contract("TroveManager", async (accounts) => { // > 150% it("checkRecoveryMode(): Returns false when TCR > 150%", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + await priceFeed.setPrice(dec(100, 18)); await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: alice } }); @@ -4977,6 +5188,9 @@ contract("TroveManager", async (accounts) => { // check 0 it("checkRecoveryMode(): Returns false when TCR == 0", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { priceFeed } = contracts; + await priceFeed.setPrice(dec(100, 18)); await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: alice } }); @@ -4994,6 +5208,9 @@ contract("TroveManager", async (accounts) => { // --- Getters --- it("getTroveStake(): Returns stake", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const { collateral: A_coll } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: A }, @@ -5011,6 +5228,9 @@ contract("TroveManager", async (accounts) => { }); it("getTroveColl(): Returns coll", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const { collateral: A_coll } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: A }, @@ -5025,6 +5245,9 @@ contract("TroveManager", async (accounts) => { }); it("getTroveDebt(): Returns debt", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + const { totalDebt: totalDebtA } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: A }, @@ -5044,6 +5267,9 @@ contract("TroveManager", async (accounts) => { }); it("getTroveStatus(): Returns status", async () => { + const { contracts, openTrove } = await loadFixture(deployContractsAndFundFixture); + const { troveManager, boldToken, borrowerOperations } = contracts; + const { totalDebt: B_totalDebt } = await openTrove({ ICR: toBN(dec(150, 16)), extraParams: { from: B }, @@ -5068,6 +5294,9 @@ contract("TroveManager", async (accounts) => { }); it("hasPendingRewards(): Returns false it trove is not active", async () => { + const { contracts } = await loadFixture(deployContractsAndFundFixture); + const { troveManager } = contracts; + assert.isFalse(await troveManager.hasPendingRewards(alice)); }); });