Submitted on Wed Jul 31 2024 07:43:42 GMT-0400 (Atlantic Standard Time) by @OxAnmol for Boost | Folks Finance
Target: https://testnet.snowtrace.io/address/0xaE4C62510F4d930a5C8796dbfB8C4Bc7b9B62140
There is no minimum amount of borrow amount set for the user, which means a user can open a dust position and will never get liquidated because of the larger gas cost.
In general, lending protocols like Aave and Compound impose a minimum borrowable amount to address the issue of small dust positions. However, in this protocol, users can open small positions without any restrictions.
This can lead to a situation where liquidators may not liquidate the debt because the gas cost is higher than the liquidation bonus.
As we can see in the console output for liquidating a $6 borrow position, the liquidator receives $0.30, which is below the average gas cost of the Avalanche blockchain. Therefore, the liquidator will never liquidate this type of position, which will eventually lead to bad debt and lender loss. With this, I think this issue should qualify as high severity.
https://github.com/Folks-Finance/folks-finance-xchain-contracts/blob/fb92deccd27359ea4f0cf0bc41394c86448c7abb/contracts/hub/LoanManager.sol#L163
Copy import { expect } from "chai" ;
import { ethers } from "hardhat" ;
import { PANIC_CODES } from "@nomicfoundation/hardhat-chai-matchers/panic" ;
import { loadFixture , reset , time } from "@nomicfoundation/hardhat-toolbox/network-helpers" ;
import {
LiquidationLogic__factory ,
LoanManagerLogic__factory ,
LoanManager__factory ,
LoanPoolLogic__factory ,
MockHubPool__factory ,
MockOracleManager__factory ,
RewardLogic__factory ,
UserLoanLogic__factory ,
} from "../../typechain-types" ;
import { BYTES32_LENGTH , convertStringToBytes , getAccountIdBytes , getEmptyBytes , getRandomBytes } from "../utils/bytes" ;
import { SECONDS_IN_DAY , SECONDS_IN_HOUR , getLatestBlockTimestamp , getRandomInt } from "../utils/time" ;
import { UserLoanBorrow , UserLoanCollateral } from "./libraries/assets/loanData" ;
import { getNodeOutputData } from "./libraries/assets/oracleData" ;
import {
calcAverageStableRate ,
calcBorrowBalance ,
calcBorrowInterestIndex ,
calcReserveCol ,
calcStableInterestRate ,
convToCollateralFAmount ,
convToRepayBorrowAmount ,
convToSeizedCollateralAmount ,
toFAmount ,
toUnderlingAmount ,
} from "./utils/formulae" ;
describe ( "Liquidation" , () => {
const DEFAULT_ADMIN_ROLE = getEmptyBytes ( BYTES32_LENGTH );
const LISTING_ROLE = ethers .keccak256 ( convertStringToBytes ( "LISTING" ));
const ORACLE_ROLE = ethers .keccak256 ( convertStringToBytes ( "ORACLE" ));
const HUB_ROLE = ethers .keccak256 ( convertStringToBytes ( "HUB" ));
async function deployLoanManagerFixture () {
const [ admin , hub , user , ... unusedUsers ] = await ethers .getSigners ();
// libraries
const userLoanLogic = await new UserLoanLogic__factory (user) .deploy ();
const userLoanLogicAddress = await userLoanLogic .getAddress ();
const loanPoolLogic = await new LoanPoolLogic__factory (user) .deploy ();
const loanPoolLogicAddress = await loanPoolLogic .getAddress ();
const liquidationLogic = await new LiquidationLogic__factory (
{
[ "contracts/hub/logic/UserLoanLogic.sol:UserLoanLogic" ] : userLoanLogicAddress ,
} ,
user
) .deploy ();
const liquidationLogicAddress = await liquidationLogic .getAddress ();
const loanManagerLogic = await new LoanManagerLogic__factory (
{
[ "contracts/hub/logic/UserLoanLogic.sol:UserLoanLogic" ] : userLoanLogicAddress ,
[ "contracts/hub/logic/LoanPoolLogic.sol:LoanPoolLogic" ] : loanPoolLogicAddress ,
[ "contracts/hub/logic/LiquidationLogic.sol:LiquidationLogic" ] : liquidationLogicAddress ,
} ,
user
) .deploy ();
const loanManagerLogicAddress = await loanManagerLogic .getAddress ();
const rewardLogic = await new RewardLogic__factory (user) .deploy ();
const rewardLogicAddress = await rewardLogic .getAddress ();
const libraries = {
userLoanLogic ,
loanPoolLogic ,
liquidationLogic ,
loanManagerLogic ,
rewardLogic ,
};
// deploy contract
const oracleManager = await new MockOracleManager__factory (user) .deploy ();
const loanManager = await new LoanManager__factory (
{
[ "contracts/hub/logic/LoanManagerLogic.sol:LoanManagerLogic" ] : loanManagerLogicAddress ,
[ "contracts/hub/logic/RewardLogic.sol:RewardLogic" ] : rewardLogicAddress ,
} ,
user
) .deploy (admin , oracleManager);
// set hub role
await loanManager .connect (admin) .grantRole ( HUB_ROLE , hub);
// common
const loanManagerAddress = await loanManager .getAddress ();
return {
admin ,
hub ,
user ,
unusedUsers ,
loanManager ,
loanManagerAddress ,
oracleManager ,
libraries ,
};
}