---
# System prepended metadata

title: Foundry DeFi Stablecoin CodeHawks Audit Contest - Findings Report

---

# Foundry DeFi Stablecoin CodeHawks Audit Contest - Findings Report

# Table of contents
- ## [Contest Summary](#contest-summary)
- ## [Results Summary](#results-summary)
- ## High Risk Findings
    - ### [H-01. `getTokenAmountFromUsd()` doesn't consider different token and oracle decimals](#H-01)
    - ### [H-02. Ensure `DSCEngine` is the owner of `DecentralizedStableCoin`](#H-02)
- ## Medium Risk Findings
    - ### [M-01. Chainlink staleness threshold should be set per asset](#M-01)
    - ### [M-02. Precision loss may occur when calculating health factor](#M-02)
    - ### [M-03. Use `safeTransfer` for token transfer](#M-03)



# <a id='contest-summary'></a>Contest Summary

### Sponsor: Cyfrin

### Dates: Jul 24th, 2023 - Aug 5th, 2023

[See more contest details here](https://www.codehawks.com/contests/cljx3b9390009liqwuedkn0m0)

# <a id='results-summary'></a>Results Summary

### Number of findings:
   - High: 2
   - Medium: 3
   - Low: 0


# High Risk Findings

## <a id='H-01'></a>H-01. `getTokenAmountFromUsd()` doesn't consider different token and oracle decimals            

### Relevant GitHub Links
	
https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L347

## Summary
`getTokenAmountFromUSD()` returns an incorrect amount for tokens with decimals != 18. It also returns an incorrect amount if chainlink oracle uses a different decimal than 8.

## Vulnerability Details
`getTokenAmountFromUSD()` calculates the final amount as follows:
```solidity
return ((usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION));
```

This is fine for current collaterals: WETH and WBTC. However, if new collateral is added in future with different decimals, the protocol will return incorrect amount to liquidator. Here is the derivation of correct formula:
- Let `C` be chainlink oracle decimal.
- Let `D` be token decimal.
- `price/1eC` USD gets 1eD tokens.
- `(price/1eC) * (usdAmountInWei/1e18)` USD gets `1eD * (usdAmountInWei/1e18)` tokens.
- `(usdAmountInWei/1e18)` USD gets:

`usdAmountInWei * 1eD * 1eC / (price * 1e18)` which is the final formula to be used in `getTokenAmountFromUsd()`.

`C` and `D` can be fetched by calling `decimals()` on the collateral token and chainlink oracle. To save gas, you can also store these values permanently.

## Impact
For tokens and chainlink oracle deviating from the 18 and 8 decimals respectively, protocol will work incorrectly.

## Tools Used
Manual review.

## Recommendations
Update the return value of [`getTokenAmountFromUsd()`](https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/DSCEngine.sol#L347) to consider token decimals and chainlink decimals.
## <a id='H-02'></a>H-02. Ensure `DSCEngine` is the owner of `DecentralizedStableCoin`            

### Relevant GitHub Links
	
https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L112

## Summary
Any address can be passed as `dscAddress` to `DSCEngine`'s constructor. So there is no guarantee that `dscAddress` is the intended stablecoin, or if `DSCEngine` is the owner of `dscAddress`. In both these cases, the protocol won't work.

## Vulnerability Details
If `DSCEngine` is not the owner of `dscAddress`, then the mint and burn calls will revert as they are guarded by `onlyOwner` modifier.

## Impact
Protocol will break, and no one can mint or burn DSC tokens.

## Tools Used
Manual review.

## Recommendations
Update `DSCEngine's` constructor to deploy `DSCEngine`. This ensures that the correct contract is deployed at `dscAddress` and the ownership is also set correctly:

```solidity
constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses) {
        // USD Price Feeds
        if (tokenAddresses.length != priceFeedAddresses.length) {
            revert DSCEngine__TokenAddressesAndPriceFeedAddressesMustBeSameLength();
        }
        // For example ETH / USD, BTC / USD, MKR / USD, etc
        for (uint256 i = 0; i < tokenAddresses.length; i++) {
            s_priceFeeds[tokenAddresses[i]] = priceFeedAddresses[i];
            s_collateralTokens.push(tokenAddresses[i]);
        }
        i_dsc = new DecentralizedStableCoin();
}
```
		
# Medium Risk Findings

## <a id='M-01'></a>M-01. Chainlink staleness threshold should be set per asset            

### Relevant GitHub Links
	
https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/main/src/libraries/OracleLib.sol#L19

## Summary
Every chainlink oracle comes with its own `heartbeat` which denotes the maximum time gap between 2 price updates. Chainlink price feeds can go stale, so the stablecoin protocol has a staleness check but the threshold is set globally and doesn't account for different oracles.

## Vulnerability Details
This protocol sets the staleness check threshold to 3 hours:
```solidity
uint256 private constant TIMEOUT = 3 hours; // 3 * 60 * 60 = 10800 seconds
```

However, for some assets this can be too long or too short. For example [ETH/USD oracle](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd) has a heartbeat of 1 hour, and stablecoin oracles usually have a heartbeat of 24 hours.

## Impact
Liquidation may occur on stale prices harming the protocol and its users.

## Tools Used
Manual review.

## Recommendations
Create a mapping to store a timeout for each collateral asset. At the time of fetching price for an asset, use the associated timeout for staleness check.
## <a id='M-02'></a>M-02. Precision loss may occur when calculating health factor            

### Relevant GitHub Links
	
https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L330

## Summary
`collateralAdjustedForThreshold` calculation can lose precision when `collateralValueInUsd * LIQUIDATION_THRESHOLD` is not exactly divisible by `LIQUIDATION_PRECISION`. The resulting value would roundoff to the lowest value and would lead to precision loss since decimals are not handled in Solidity.


## Vulnerability Details
```solidity
uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
return (collateralAdjustedForThreshold * 1e18) / totalDscMinted;
```

For example, for `collateralValueInUsd = 267`, `collateralAdjustedForThreshold = 133` when the actual value should be `133.5`.

## Impact
Calculated health factor will be lower than the real value.

## Tools Used
Manual review 

## Recommendations
We multiply `1e18` with `collateralAdjustedForThreshold`. Instead, first multiply and calculate the entire numerator before dividing:

```diff
+return (collateralValueInUsd * LIQUIDATION_THRESHOLD * 1e18) / (totalDscMinted * LIQUIDATION_PRECISION);
-uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
-return (collateralAdjustedForThreshold * 1e18) / totalDscMinted;
```

## <a id='M-03'></a>M-03. Use `safeTransfer` for token transfer            

### Relevant GitHub Links
	
https://github.com/Cyfrin/2023-07-foundry-defi-stablecoin/blob/d1c5501aa79320ca0aeaa73f47f0dbc88c7b77e2/src/DSCEngine.sol#L157

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

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

## Summary
If non-compliant ERC20 tokens are added in future as collateral (like USDT), then protocol may not be able to handle the transfers.

## Vulnerability Details
The protocol use `transfer` and `transferFrom` to transfer collateral token. It also verifies that its return value is `true`:
```solidity
bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
if (!success) {
    revert DSCEngine__TransferFailed();
}
```
For WETH and WBTC, if transfer doesn't revert, `success` is always true. Hence this check can be skipped to save gas. However, if we want to add other collateral tokens in future, it's recommended to use OpenZeppelin's `SafeERC20` library which accommodates non-compliant ERC20 tokens. For example, USDT which doesn't return anything on a successful transfer.


## Impact

## Tools Used
Manual review.

## Recommendations
Use OpenZeppelin's [`SafeERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol#L36-L44) library to handle ERC20 transfer. Add the following at the top of the contract:
```solidity
using SafeERC20 for IERC20;
```

Now replace `.transfer` and `.transferFrom` with `.safeTransfer` and `.safeTransferFrom`. For instance:
```diff
+IERC20(tokenCollateralAddress).safeTransferFrom(msg.sender, address(this), amountCollateral);
-bool success = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
-if (!success) {
-    revert DSCEngine__TransferFailed();
-}
```




