Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
6 changes: 6 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"name": "v1.12.0",
"rev": "7117c90c8cf6c68e5acce4f09a6b24715cea4de6"
}
},
"lib/openzeppelin-contracts": {
"tag": {
"name": "v5.5.0",
"rev": "fcbae5394ae8ad52d8e580a3477db99814b9d565"
}
}
}
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
src = "src"
out = "out"
libs = ["lib"]

remappings = ["@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at fcbae5
132 changes: 132 additions & 0 deletions src/libraries/HealthFactor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol";

// HF & debt limits round down; required collateral rounds up (safer).
library HealthFactor {
/*//////////////////////////////////////////////////////////////
CONSTANT
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18;
uint256 internal constant MIN_HF = 1e18;

/*//////////////////////////////////////////////////////////////
STRUCT
//////////////////////////////////////////////////////////////*/
struct HealthFactorSnapshot {
uint256 collateralValue;
uint256 debt;
uint256 lt;
uint256 hf;
}
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error InvalidLiquidationThreshold();
error InvalidTargetHealthFactor();

/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
//hfwad = collateralValueWad*liquidationThresholdWad/debtWad
function healthFactor(uint256 collateralValueWad, uint256 debtWad, uint256 liquidationThresholdWad)
internal
pure
returns (uint256 hfWad)
{
if (liquidationThresholdWad == 0 || liquidationThresholdWad > WAD) revert InvalidLiquidationThreshold();
if (debtWad == 0) return type(uint256).max;
if (collateralValueWad == 0) return 0;
return Math.mulDiv(collateralValueWad, liquidationThresholdWad, debtWad);
}

function isHealthy(uint256 hfWad) internal pure returns (bool) {
return hfWad >= MIN_HF;
}

function isLiquidatable(uint256 hfWad) internal pure returns (bool) {
return hfWad < MIN_HF;
}

function maxDebt(uint256 collateralValueWad, uint256 liquidationThresholdWad)
internal
pure
returns (uint256 maxDebtWad)
{
if (liquidationThresholdWad == 0 || liquidationThresholdWad > WAD) revert InvalidLiquidationThreshold();
return Math.mulDiv(collateralValueWad, liquidationThresholdWad, WAD);
}

function requiredDebtForTargetHF(uint256 collateralValueWad, uint256 liquidationThresholdWad, uint256 targetHfWad)
internal
pure
returns (uint256 requiredDebtWad)
{
validateParams(liquidationThresholdWad, targetHfWad);
return Math.mulDiv(collateralValueWad, liquidationThresholdWad, targetHfWad);
}

function debtToCoverForTargetHF(
uint256 collateralValueWad,
uint256 debtWad,
uint256 liquidationThresholdWad,
uint256 targetHfWad
) internal pure returns (uint256 coverDebtWad) {
uint256 requiredDebt = requiredDebtForTargetHF(collateralValueWad, liquidationThresholdWad, targetHfWad);
if (debtWad <= requiredDebt) {
return 0;
}

return debtWad - requiredDebt;
}

function requiredCollateralValueForTargetHF(uint256 debtWad, uint256 liquidationThresholdWad, uint256 targetHfWad)
internal
pure
returns (uint256 requiredCollateralValueWad)
{
if (debtWad == 0) return 0;
validateParams(liquidationThresholdWad, targetHfWad);
return Math.mulDiv(targetHfWad, debtWad, liquidationThresholdWad, Math.Rounding.Ceil);
}

function collateralShortfallForTargetHF(
uint256 collateralValueWad,
uint256 debtWad,
uint256 liquidationThresholdWad,
uint256 targetHfWad
) internal pure returns (uint256 shortfallWad) {
uint256 requiredCollateralValue =
requiredCollateralValueForTargetHF(debtWad, liquidationThresholdWad, targetHfWad);

if (collateralValueWad >= requiredCollateralValue) {
return 0;
}

return requiredCollateralValue - collateralValueWad;
}

function healthFactorAndStatus(uint256 collateralValueWad, uint256 debtWad, uint256 liquidationThresholdWad)
internal
pure
returns (uint256 hfWad, bool liquidatable)
{
if (liquidationThresholdWad == 0 || liquidationThresholdWad > WAD) revert InvalidLiquidationThreshold();
if (debtWad == 0) return (type(uint256).max, false);
if (collateralValueWad == 0) return (0, true);
hfWad = healthFactor(collateralValueWad, debtWad, liquidationThresholdWad);
liquidatable = isLiquidatable(hfWad);
}

function validateParams(uint256 liquidationThresholdWad, uint256 targetHfWad) internal pure {
if (liquidationThresholdWad == 0 || liquidationThresholdWad > WAD) {
revert InvalidLiquidationThreshold();
}

if (targetHfWad < MIN_HF) {
revert InvalidTargetHealthFactor();
}
}
}
Empty file removed src/libraries/SafeCast.sol
Empty file.
63 changes: 63 additions & 0 deletions src/libraries/WadRayMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;
/**
* @title WadRayMath
* @author Wentao Lin
* @notice Fixed-point math library for WAD (1e18) and RAY (1e27) precision
* @dev
* - WAD is used for token amounts, prices, and ratios (18 decimals)
* - RAY is used for indices and time-accumulating values (27 decimals)
* - Provides multiplication and division helpers with round-half-up semantics
*/

library WadRayMath {
/*//////////////////////////////////////////////////////////////
CONSTANT
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18;
uint256 internal constant HALF_WAD = 5e17;
uint256 internal constant RAY = 1e27;
uint256 internal constant HALF_RAY = 5e26;
uint256 internal constant WAD_RAY_RATIO = 1e9;

/*//////////////////////////////////////////////////////////////
ERROR
//////////////////////////////////////////////////////////////*/
error DevisionByZero();

/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
return (a * b + HALF_WAD) / WAD;
}

function wadDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return (b / 2 + (a * WAD)) / b;
}

function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
return (a * b + HALF_RAY) / RAY;
}

function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return (b / 2 + a * RAY) / b;
}

function wadToRay(uint256 wad) internal pure returns (uint256) {
return wad * WAD_RAY_RATIO;
}

function rayToWad(uint256 ray) internal pure returns (uint256) {
return (ray + WAD_RAY_RATIO / 2) / WAD_RAY_RATIO;
}

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}

function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
}