Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: optimize gas #117

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
212 changes: 140 additions & 72 deletions src/DSCEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@

pragma solidity 0.8.19;

import { OracleLib, AggregatorV3Interface } from "./libraries/OracleLib.sol";
// The correct path for ReentrancyGuard in latest Openzeppelin contracts is
import {OracleLib, AggregatorV3Interface} from "./libraries/OracleLib.sol";
// The correct path for ReentrancyGuard in latest Openzeppelin contracts is
//"import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { DecentralizedStableCoin } from "./DecentralizedStableCoin.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {DecentralizedStableCoin} from "./DecentralizedStableCoin.sol";

/*
* @title DSCEngine
Expand Down Expand Up @@ -84,7 +84,8 @@ contract DSCEngine is ReentrancyGuard {
/// @dev Mapping of token address to price feed address
mapping(address collateralToken => address priceFeed) private s_priceFeeds;
/// @dev Amount of collateral deposited by user
mapping(address user => mapping(address collateralToken => uint256 amount)) private s_collateralDeposited;
mapping(address user => mapping(address collateralToken => uint256 amount))
private s_collateralDeposited;
/// @dev Amount of DSC minted by user
mapping(address user => uint256 amount) private s_DSCMinted;
/// @dev If we know exactly how many tokens we have, we could make this immutable!
Expand All @@ -93,9 +94,18 @@ contract DSCEngine is ReentrancyGuard {
///////////////////
// Events
///////////////////
event CollateralDeposited(address indexed user, address indexed token, uint256 indexed amount);
event CollateralRedeemed(address indexed redeemFrom, address indexed redeemTo, address token, uint256 amount); // if
// redeemFrom != redeemedTo, then it was liquidated
event CollateralDeposited(
address indexed user,
address indexed token,
uint256 indexed amount
);
event CollateralRedeemed(
address indexed redeemFrom,
address indexed redeemTo,
address token,
uint256 amount
); // if
// redeemFrom != redeemedTo, then it was liquidated

///////////////////
// Modifiers
Expand All @@ -117,13 +127,17 @@ contract DSCEngine is ReentrancyGuard {
///////////////////
// Functions
///////////////////
constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, address dscAddress) {
constructor(
address[] memory tokenAddresses,
address[] memory priceFeedAddresses,
address dscAddress
) {
if (tokenAddresses.length != priceFeedAddresses.length) {
revert DSCEngine__TokenAddressesAndPriceFeedAddressesAmountsDontMatch();
}
// These feeds will be the USD pairs
// For example ETH / USD or MKR / USD
for (uint256 i = 0; i < tokenAddresses.length; i++) {
for (uint256 i; i < tokenAddresses.length; ++i) {
s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
s_collateralTokens.push(tokenAddresses[i]);
}
Expand All @@ -143,9 +157,7 @@ contract DSCEngine is ReentrancyGuard {
address tokenCollateralAddress,
uint256 amountCollateral,
uint256 amountDscToMint
)
external
{
) external {
depositCollateral(tokenCollateralAddress, amountCollateral);
mintDsc(amountDscToMint);
}
Expand All @@ -165,9 +177,10 @@ contract DSCEngine is ReentrancyGuard {
moreThanZero(amountCollateral)
isAllowedToken(tokenCollateralAddress)
{
_burnDsc(amountDscToBurn, msg.sender, msg.sender);
_redeemCollateral(tokenCollateralAddress, amountCollateral, msg.sender, msg.sender);
revertIfHealthFactorIsBroken(msg.sender);
address user = msg.sender;
_burnDsc(amountDscToBurn, user, user);
_redeemCollateral(tokenCollateralAddress, amountCollateral, user, user);
revertIfHealthFactorIsBroken(user);
}

/*
Expand All @@ -185,8 +198,9 @@ contract DSCEngine is ReentrancyGuard {
nonReentrant
isAllowedToken(tokenCollateralAddress)
{
_redeemCollateral(tokenCollateralAddress, amountCollateral, msg.sender, msg.sender);
revertIfHealthFactorIsBroken(msg.sender);
address user = msg.sender;
_redeemCollateral(tokenCollateralAddress, amountCollateral, user, user);
revertIfHealthFactorIsBroken(user);
}

/*
Expand All @@ -195,8 +209,9 @@ contract DSCEngine is ReentrancyGuard {
* your DSC but keep your collateral in.
*/
function burnDsc(uint256 amount) external moreThanZero(amount) {
_burnDsc(amount, msg.sender, msg.sender);
revertIfHealthFactorIsBroken(msg.sender); // I don't think this would ever hit...
address user = msg.sender;
_burnDsc(amount, user, user);
revertIfHealthFactorIsBroken(user); // I don't think this would ever hit...
}

/*
Expand Down Expand Up @@ -224,28 +239,38 @@ contract DSCEngine is ReentrancyGuard {
moreThanZero(debtToCover)
nonReentrant
{
address liquidator = msg.sender;
uint256 startingUserHealthFactor = _healthFactor(user);
if (startingUserHealthFactor >= MIN_HEALTH_FACTOR) {
revert DSCEngine__HealthFactorOk();
}
// If covering 100 DSC, we need to $100 of collateral
uint256 tokenAmountFromDebtCovered = getTokenAmountFromUsd(collateral, debtToCover);
uint256 tokenAmountFromDebtCovered = getTokenAmountFromUsd(
collateral,
debtToCover
);
// And give them a 10% bonus
// So we are giving the liquidator $110 of WETH for 100 DSC
// We should implement a feature to liquidate in the event the protocol is insolvent
// And sweep extra amounts into a treasury
uint256 bonusCollateral = (tokenAmountFromDebtCovered * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
uint256 bonusCollateral = (tokenAmountFromDebtCovered *
LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
// Burn DSC equal to debtToCover
// Figure out how much collateral to recover based on how much burnt
_redeemCollateral(collateral, tokenAmountFromDebtCovered + bonusCollateral, user, msg.sender);
_burnDsc(debtToCover, user, msg.sender);
_redeemCollateral(
collateral,
tokenAmountFromDebtCovered + bonusCollateral,
user,
liquidator
);
_burnDsc(debtToCover, user, liquidator);

uint256 endingUserHealthFactor = _healthFactor(user);
// This conditional should never hit, but just in case
if (endingUserHealthFactor <= startingUserHealthFactor) {
revert DSCEngine__HealthFactorNotImproved();
}
revertIfHealthFactorIsBroken(msg.sender);
revertIfHealthFactorIsBroken(liquidator);
}

///////////////////
Expand All @@ -255,12 +280,15 @@ contract DSCEngine is ReentrancyGuard {
* @param amountDscToMint: The amount of DSC you want to mint
* You can only mint DSC if you have enough collateral
*/
function mintDsc(uint256 amountDscToMint) public moreThanZero(amountDscToMint) nonReentrant {
s_DSCMinted[msg.sender] += amountDscToMint;
revertIfHealthFactorIsBroken(msg.sender);
bool minted = i_dsc.mint(msg.sender, amountDscToMint);
function mintDsc(
uint256 amountDscToMint
) public moreThanZero(amountDscToMint) nonReentrant {
address user = msg.sender;
s_DSCMinted[user] += amountDscToMint;
revertIfHealthFactorIsBroken(user);
bool minted = i_dsc.mint(user, amountDscToMint);

if (minted != true) {
if (!minted) {
revert DSCEngine__MintFailed();
}
}
Expand All @@ -278,9 +306,20 @@ contract DSCEngine is ReentrancyGuard {
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);
address depositor = msg.sender;
s_collateralDeposited[depositor][
tokenCollateralAddress
] += amountCollateral;
emit CollateralDeposited(
depositor,
tokenCollateralAddress,
amountCollateral
);
bool success = IERC20(tokenCollateralAddress).transferFrom(
depositor,
address(this),
amountCollateral
);
if (!success) {
revert DSCEngine__TransferFailed();
}
Expand All @@ -294,21 +333,35 @@ contract DSCEngine is ReentrancyGuard {
uint256 amountCollateral,
address from,
address to
)
private
{
) private {
s_collateralDeposited[from][tokenCollateralAddress] -= amountCollateral;
emit CollateralRedeemed(from, to, tokenCollateralAddress, amountCollateral);
bool success = IERC20(tokenCollateralAddress).transfer(to, amountCollateral);
emit CollateralRedeemed(
from,
to,
tokenCollateralAddress,
amountCollateral
);
bool success = IERC20(tokenCollateralAddress).transfer(
to,
amountCollateral
);
if (!success) {
revert DSCEngine__TransferFailed();
}
}

function _burnDsc(uint256 amountDscToBurn, address onBehalfOf, address dscFrom) private {
function _burnDsc(
uint256 amountDscToBurn,
address onBehalfOf,
address dscFrom
) private {
s_DSCMinted[onBehalfOf] -= amountDscToBurn;

bool success = i_dsc.transferFrom(dscFrom, address(this), amountDscToBurn);
bool success = i_dsc.transferFrom(
dscFrom,
address(this),
amountDscToBurn
);
// This conditional is hypothetically unreachable
if (!success) {
revert DSCEngine__TransferFailed();
Expand All @@ -320,7 +373,9 @@ contract DSCEngine is ReentrancyGuard {
// Private & Internal View & Pure Functions
//////////////////////////////

function _getAccountInformation(address user)
function _getAccountInformation(
address user
)
private
view
returns (uint256 totalDscMinted, uint256 collateralValueInUsd)
Expand All @@ -330,30 +385,36 @@ contract DSCEngine is ReentrancyGuard {
}

function _healthFactor(address user) private view returns (uint256) {
(uint256 totalDscMinted, uint256 collateralValueInUsd) = _getAccountInformation(user);
(
uint256 totalDscMinted,
uint256 collateralValueInUsd
) = _getAccountInformation(user);
return _calculateHealthFactor(totalDscMinted, collateralValueInUsd);
}

function _getUsdValue(address token, uint256 amount) private view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
function _getUsdValue(
address token,
uint256 amount
) private view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(
s_priceFeeds[token]
);
(, int256 price, , , ) = priceFeed.staleCheckLatestRoundData();
// 1 ETH = 1000 USD
// The returned value from Chainlink will be 1000 * 1e8
// Most USD pairs have 8 decimals, so we will just pretend they all do
// We want to have everything in terms of WEI, so we add 10 zeros at the end
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
return
((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
}

function _calculateHealthFactor(
uint256 totalDscMinted,
uint256 collateralValueInUsd
)
internal
pure
returns (uint256)
{
) internal pure returns (uint256) {
if (totalDscMinted == 0) return type(uint256).max;
uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
uint256 collateralAdjustedForThreshold = (collateralValueInUsd *
LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
return (collateralAdjustedForThreshold * PRECISION) / totalDscMinted;
}

Expand All @@ -372,15 +433,13 @@ contract DSCEngine is ReentrancyGuard {
function calculateHealthFactor(
uint256 totalDscMinted,
uint256 collateralValueInUsd
)
external
pure
returns (uint256)
{
) external pure returns (uint256) {
return _calculateHealthFactor(totalDscMinted, collateralValueInUsd);
}

function getAccountInformation(address user)
function getAccountInformation(
address user
)
external
view
returns (uint256 totalDscMinted, uint256 collateralValueInUsd)
Expand All @@ -391,35 +450,42 @@ contract DSCEngine is ReentrancyGuard {
function getUsdValue(
address token,
uint256 amount // in WEI
)
external
view
returns (uint256)
{
) external view returns (uint256) {
return _getUsdValue(token, amount);
}

function getCollateralBalanceOfUser(address user, address token) external view returns (uint256) {
function getCollateralBalanceOfUser(
address user,
address token
) external view returns (uint256) {
return s_collateralDeposited[user][token];
}

function getAccountCollateralValue(address user) public view returns (uint256 totalCollateralValueInUsd) {
for (uint256 index = 0; index < s_collateralTokens.length; index++) {
function getAccountCollateralValue(
address user
) public view returns (uint256 totalCollateralValueInUsd) {
for (uint256 index; index < s_collateralTokens.length; ++index) {
address token = s_collateralTokens[index];
uint256 amount = s_collateralDeposited[user][token];
totalCollateralValueInUsd += _getUsdValue(token, amount);
}
return totalCollateralValueInUsd;
}

function getTokenAmountFromUsd(address token, uint256 usdAmountInWei) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
function getTokenAmountFromUsd(
address token,
uint256 usdAmountInWei
) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(
s_priceFeeds[token]
);
(, int256 price, , , ) = priceFeed.staleCheckLatestRoundData();
// $100e18 USD Debt
// 1 ETH = 2000 USD
// The returned value from Chainlink will be 2000 * 1e8
// Most USD pairs have 8 decimals, so we will just pretend they all do
return ((usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION));
return ((usdAmountInWei * PRECISION) /
(uint256(price) * ADDITIONAL_FEED_PRECISION));
}

function getPrecision() external pure returns (uint256) {
Expand Down Expand Up @@ -454,7 +520,9 @@ contract DSCEngine is ReentrancyGuard {
return address(i_dsc);
}

function getCollateralTokenPriceFeed(address token) external view returns (address) {
function getCollateralTokenPriceFeed(
address token
) external view returns (address) {
return s_priceFeeds[token];
}

Expand Down