Submitted on Fri Aug 02 2024 02:04:59 GMT-0400 (Atlantic Standard Time) by @jovi for Boost | Folks Finance
In liquidations, the borrower repays his debt with some of his collateral plus a bonus amount as incentives to the liquidator and as fees to the protocol. The executeLiquidate function, however, does not ensure the liquidation makes the borrower's position healthy, leaving the position exposed to repeated liquidations.
The executeLiquidate function at the LoanManagerLogic contract coordinates the main logic behind liquidations. During its final checks, it ensures the liquidator's loan is over-collateralized after taking over part of the validator loan:
Copy // check liquidator loan in over-collateralized after taking over part of the violator loan
loansParams. checkLiquidatorLoan (userLoans , loanTypes , pools , params.oracleManager);
However, the violator loan not only has lost some borrowed amount, but also some collateral amount. That means the violator's position is not necessarily healthier than it was before as ought to be checked if it is over-collateralized as well after the liquidation happens.
Liquidators may liquidate a violator's loan to states that are less healthy than before and start a loop of liquidations till all the violator loan is drained.
Paste the following test inside the Liquidate tests at the LoanManager.test.ts file:
Copy it ( "Should liquidate variable borrow multiple times while seizing borrow and collateral j-01" , async () => {
const {
hub ,
loanManager ,
loanManagerAddress ,
oracleManager ,
pools ,
loanId: violatorLoanId ,
accountId: violatorAccountId ,
loanTypeId ,
depositFAmount ,
borrowAmount ,
usdcVariableInterestIndex: oldVariableInterestIndex ,
} = await loadFixture (depositEtherAndVariableBorrowUSDCFixture);
// create liquidator loan
const liquidatorLoanId = getRandomBytes ( BYTES32_LENGTH );
const liquidatorAccountId = getAccountIdBytes ( "LIQUIDATOR_ACCOUNT_ID" );
const liquidatorLoanName = getRandomBytes ( BYTES32_LENGTH );
await loanManager
.connect (hub)
.createUserLoan (liquidatorLoanId , liquidatorAccountId , loanTypeId , liquidatorLoanName);
// deposit USDC into liquidator loan
const liquidatorDepositAmount = BigInt ( 1000e6 ); // 10,000 USDC
const liquidatorDepositFAmount = liquidatorDepositAmount;
const liquidatorDepositInterestIndex = BigInt ( 1e18 );
const usdcPrice = BigInt ( 1e18 );
await pools . USDC . pool .setDepositPoolParams ({
fAmount : liquidatorDepositFAmount ,
depositInterestIndex : liquidatorDepositInterestIndex ,
priceFeed : { price : usdcPrice , decimals : pools . USDC .tokenDecimals } ,
});
await loanManager
.connect (hub)
.deposit (liquidatorLoanId , liquidatorAccountId , pools . USDC .poolId , liquidatorDepositAmount);
// prepare liquidation
const ethNodeOutputData = getNodeOutputData ( BigInt ( 1000e18 ));
await oracleManager .setNodeOutput ( pools . ETH .poolId , pools . ETH .tokenDecimals , ethNodeOutputData);
// calculate interest
const variableInterestIndex = BigInt ( 1.1e18 );
const stableInterestRate = BigInt ( 0.1e18 );
await pools . USDC . pool .setBorrowPoolParams ({ variableInterestIndex , stableInterestRate });
await pools . USDC . pool .setUpdatedVariableBorrowInterestIndex (variableInterestIndex);
// Violator:
// Collateral 1 ETH = $1,000
// Borrow 1,000 USDC = $1,000
// Liquidator:
// Collateral 10,000 USDC = $10,000
// Borrow $0
const repayAmount = BigInt ( 100e6 ); // 100 USDC
const collateralFAmount = convToCollateralFAmount (
repayAmount ,
ethNodeOutputData .price ,
pools . ETH .tokenDecimals ,
usdcPrice ,
pools . USDC .tokenDecimals ,
BigInt ( 1e18 )
);
const seizeCollateralAmount = convToSeizedCollateralAmount (
repayAmount ,
ethNodeOutputData .price ,
pools . ETH .tokenDecimals ,
usdcPrice ,
pools . USDC .tokenDecimals ,
pools . USDC .liquidationBonus
);
const seizeCollateralFAmount = toFAmount (seizeCollateralAmount , BigInt ( 1e18 ));
const reserveCollateralFAmount = calcReserveCol (
seizeCollateralFAmount ,
collateralFAmount ,
pools . ETH .liquidationFee
);
// liquidate
const minSeizedAmount = BigInt ( 0 );
const liquidate = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
let borrowHealthFactor;
let violatorLoanNew;
const liquidate2 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate3 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate5 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate6 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate7 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate8 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate9 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate10 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
// violator collateral / violator borrow balance
borrowHealthFactor = violatorLoanNew[ 4 ][ 0 ][ 0 ] / violatorLoanNew[ 5 ][ 0 ][ 1 ];
console .log ({borrowHealthFactor});
const liquidate11 = await loanManager
.connect (hub)
.liquidate (
violatorLoanId ,
liquidatorLoanId ,
liquidatorAccountId ,
pools . ETH .poolId ,
pools . USDC .poolId ,
repayAmount ,
minSeizedAmount
);
violatorLoanNew = await loanManager .getUserLoan (violatorLoanId);
console.log("\n Take a look at the violator loan after all the liquidations with the all the collateral removed \n");
console .log ({violatorLoanNew});
});
Copy npx hardhat test --grep "Should liquidate variable borrow multiple times while seizing borrow and collateral j-01"