1/5
_Follow along the course with this video._ --- ### Health Factor In the previous lesson we walked through the mintDsc function and a **_bunch_** of additional functions on which that operation depends. We briefly skimmed over the `Health Factor` of an account, and in this lesson we'll dive deeper into this concept and write the functions necessary to determine an account's `Health Factor`. So far, our `_healthFactor` function is only acquiring the user's `totalDscMinted` and the `collateralValueInUsd`. What we can now do, is take the ratio of these two. An account's `Health Factor` will be a bit more complex to consider than simply `collateralValueInUsd / totalDscMinted`. Remember, we want to assure the protocol is always `over-collateralized`, and to do this, there needs to be a threshold determined that this ratio needs to adhere to, 200% for example. We can set this threshold via a constant state variable. ```js ///////////////////////// // State Variables // ///////////////////////// mapping(address token => address priceFeed) private s_priceFeeds; DecentralizedStableCoin private immutable i_dsc; mapping(address user => mapping(address token => uint256 amount)) private s_collateralDeposited; mapping(address user => uint256 amountDscMinted) private s_DSCMinted; address[] private s_collateralTokens; uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10; uint256 private constant PRECISION = 1e18; uint256 private constant LIQUIDATION_THRESHOLD = 50; uint256 private constant LIQUIDATION_PRECISION = 100; ``` The threshold above, set at `50`, will assure a user's position is `200%` `over-collateralized`. We've also declared a `LIQUIDATION_PRECISION` constant for use in our calculation. We can apply this to our function's calculation now. ```js /* * Returns how close to liquidation a user is * If a user goes below 1, then they can be liquidated. */ function _healthFactor(address user) private view returns(uint256){ (uint256 totalDscMinted, uint256 collateralValueInUsd) = _getAccountInformation(user); uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION; } ``` Let's work this `Health Factor` calculation out mathematically with an example. Say a user deposits $150 worth of ETH and goes to mint $100 worth of DSC. ``` (150 * 50) / 100 = 75 75/100 = 0.75 0.75 < 1 ``` In the above example, a user who has deposited $150 worth of ETH would not be able to mint $100 worth of DSC as it results in their `Health Factor` breaking. $100 in DSC requires $200 in collateral to be deposited for the `Health Factor` to remain above 1. ``` (200 * 50) / 100 = 100 100/100 = 1 1 >= 1 ``` With a `LIQUIDATION_THRESHOLD` of 50, a user requires 200% over-collateralization of their position, or the risk liquidation. Now that we've adjusted our collateral amount to account for a position's `LIQUIDATION_THRESHOLD`, we can use this adjust value to calculate a user's true `Health Factor`. ```js /* * Returns how close to liquidation a user is * If a user goes below 1, then they can be liquidated. */ function _healthFactor(address user) private view returns(uint256){ (uint256 totalDscMinted, uint256 collateralValueInUsd) = _getAccountInformation(user); uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION; return (collateralAdjustedForThreshold * PRECISION) / totalDscMinted; } ``` To apply our new return calculation to our above examples: ``` (150 * 50) / 100 = 75 return (75 * 1e18) / 100e18 return (0.75) ``` Alright! Now, we've been talking about `Health Factors` which are `< 1` as being at risk of liquidation. We should set this constant officially with a state variable before moving on. We'll need it in our `_revertIfHealthFactorIsBroken` function. ```js ///////////////////////// // State Variables // ///////////////////////// mapping(address token => address priceFeed) private s_priceFeeds; DecentralizedStableCoin private immutable i_dsc; mapping(address user => mapping(address token => uint256 amount)) private s_collateralDeposited; mapping(address user => uint256 amountDscMinted) private s_DSCMinted; address[] private s_collateralTokens; uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10; uint256 private constant PRECISION = 1e18; uint256 private constant LIQUIDATION_THRESHOLD = 50; uint256 private constant LIQUIDATION_PRECISION = 100; uint256 private constant MIN_HEALTH_FACTOR = 1e18; ``` We're ready to put our `_healthFactor` function and our `MIN_HEALTH_FACTOR` constant to work. We can use these to declare a conditional statemment within `_revertIfHealthFactorIsBroken`, which will revert with a custom error if the conditional fails to pass. ```js function _revertIfHealthFactorIsBroken(address user) internal view { uint256 userHealthFactor = _healthFactor(user); if(userHealthFactor < MIN_HEALTH_FACTOR){ revert DSCEngine__BreaksHealthFactor(userHealthFactor); } } ``` Don't forget to add the custom error to the top of our contract with the others. ```js /////////////////// // Errors // /////////////////// error DSCEngine__TokenAddressesAndPriceFeedAddressesAmountsDontMatch(); error DSCEngine__NeedsMoreThanZero(); error DSCEngine__TokenNotAllowed(address token); error DSCEngine__TransferFailed(); error DSCEngine__BreaksHealthFactor(uint256 healthFactor); ``` ### Wrap Up Another function down! We're killing it. We should assure things are compiling properly with `forge build`. If you run into any compilation errors, please reference the contract below which should reflect the state we're at currently. In the next lesson, we finish the `mintDsc` function! See you there! <details> <summary>DSCEngine.sol</summary> ```js // Layout of Contract: // version // imports // errors // interfaces, libraries, contracts // Type declarations // State variables // Events // Modifiers // Functions // Layout of Functions: // constructor // receive function (if exists) // fallback function (if exists) // external // public // internal // private // internal & private view & pure functions // external & public view & pure functions // SPDX-License-Identifier: MIT pragma solidity 0.8.18; import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { DecentralizedStableCoin } from "./DecentralizedStableCoin.sol"; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; /* * @title DSCEngine * @author Patrick Collins * * The system is designed to be as minimal as possible, and have the tokens maintain a 1 token == $1 peg at all times. * This is a stablecoin with the properties: * - Exogenously Collateralized * - Dollar Pegged * - Algorithmically Stable * * It is similar to DAI if DAI had no governance, no fees, and was backed by only WETH and WBTC. * * Our DSC system should always be "overcollateralized". At no point, should the value of * all collateral < the $ backed value of all the DSC. * * @notice This contract is the core of the Decentralized Stablecoin system. It handles all the logic * for minting and redeeming DSC, as well as depositing and withdrawing collateral. * @notice This contract is based on the MakerDAO DSS system */ contract DSCEngine is ReentrancyGuard { /////////////////// // Errors // /////////////////// error DSCEngine__TokenAddressesAndPriceFeedAddressesAmountsDontMatch(); error DSCEngine__NeedsMoreThanZero(); error DSCEngine__TokenNotAllowed(address token); error DSCEngine__TransferFailed(); error DSCEngine__BreaksHealthFactor(uint256 healthFactor); ///////////////////////// // State Variables // ///////////////////////// mapping(address token => address priceFeed) private s_priceFeeds; DecentralizedStableCoin private immutable i_dsc; mapping(address user => mapping(address token => uint256 amount)) private s_collateralDeposited; mapping(address user => uint256 amountDscMinted) private s_DSCMinted; address[] private s_collateralTokens; uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10; uint256 private constant PRECISION = 1e18; uint256 private constant LIQUIDATION_THRESHOLD = 50; uint256 private constant LIQUIDATION_PRECISION = 100; uint256 private constant MIN_HEALTH_FACTOR = 1e18; //////////////// // Events // //////////////// event CollateralDeposited(address indexed user, address indexed token, uint256 indexed amount); /////////////////// // Modifiers // /////////////////// modifier moreThanZero(uint256 amount){ if(amount <=0){ revert DSCEngine__NeedsMoreThanZero(); } _; } modifier isAllowedToken(address token) { if (s_priceFeeds[token] == address(0)) { revert DSCEngine__TokenNotAllowed(token); } _; } /////////////////// // Functions // /////////////////// constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, address dscAddress){ if(tokenAddresses.length != priceFeedAddresses.length){ revert DSCEngine__TokenAddressesAndPriceFeedAddressesMustBeSameLength(); } for(uint256 i=0; i < tokenAddresses.length; i++){ s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i]; s_collateralTokens.push(tokenAddresses[i]); } i_dsc = DecentralizedStableCoin(dscAddress); } /////////////////////////// // External Functions // /////////////////////////// /* * @param tokenCollateralAddress: The ERC20 token address of the collateral you're depositing * @param amountCollateral: The amount of collateral you're depositing */ function depositCollateral( address tokenCollateralAddress, uint256 amountCollateral ) external moreThanZero(amountCollateral) nonReentrant isAllowedToken(tokenCollateralAddress) { s_collateralDeposited[msg.sender][tokenCollateralAddress] += amountCollateral; emit CollateralDeposited(msg.sender, tokenCollateralAddress, amountCollateral); bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral); if (!success) { revert DSCEngine__TransferFailed(); } } /* * @param amountDscToMint: The amount of DSC you want to mint * You can only mint DSC if you hav enough collateral */ function mintDsc(uint256 amountDscToMint) public moreThanZero(amountDscToMint) nonReentrant { s_DSCMinted[msg.sender] += amountDscToMint; } /////////////////////////////////////////// // Private & Internal View Functions // /////////////////////////////////////////// /* * Returns how close to liquidation a user is * If a user goes below 1, then they can be liquidated. */ function _healthFactor(address user) private view returns(uint256){ (uint256 totalDscMinted, uint256 collateralValueInUsd) = _getAccountInformation(user); uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION; return (collateralAdjustedForThreshold * PRECISION) / totalDscMinted; } function _getAccountInformation(address user) private view returns(uint256 totalDscMinted,uint256 collateralValueInUsd){ totalDscMinted = s_DSCMinted[user]; collateralValueInUsd = getAccountCollateralValue(user); } function _revertIfHealthFactorIsBroken(address user) internal view { uint256 userHealthFactor = _healthFactor(user); if(userHealthFactor < MIN_HEALTH_FACTOR){ revert DSCEngine__BreaksHealthFactor(userHealthFactor); } } ////////////////////////////////////////// // Public & External View Functions // ////////////////////////////////////////// function getAccountCollateralValue(address user) public view returns (uint256 totalCollateralValueInUsd) { for(uint256 i = 0; i < s_collateralTokens.length; i++){ address token = s_collateralTokens[i]; uint256 amount = s_collateralDeposited[user][token]; totalCollateralValueInUsd += getUsdValue(token, amount); } return totalCollateralValueInUsd; } function getUsdValue(address token, uint256 amount) public view returns(uint256){ AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]); (,int256 price,,,) = priceFeed.latestRoundData(); return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION; } function depositCollaterAndMintDsc() external {} function redeemCollateralForDsc() external {} function redeemCollateral() external {} function burnDsc() external {} function liquidate() external {} function getHealthFactor() external view {} } ``` </details>
Delve into the concept of 'Health Factor' in DeFi protocols, its calculation, significance, and impact on the stability and risk management of DeFi projects.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at Avara
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on November 29, 2024
Solidity Developer
Advanced FoundryDuration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 58min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at Avara
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on November 29, 2024
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar