A fully decentralized, algorithmic stablecoin system pegged to USD, backed by WETH and WBTC collateral. Built with Foundry. 🚀
Interact with the live deployed contracts on Sepolia testnet
Connect MetaMask to interact with the DSC protocol on Sepolia
⚠️ Make sure you're on Sepolia Testnet
Mint DSC tokens against your collateral (maintains health factor)
Health Factor determines if your position is safe or can be liquidated. It's calculated based on your collateral value and debt.
Core protocol logic - handles deposits, minting, burning, and liquidations
ERC20 stablecoin token - only DSCEngine can mint/burn
Chainlink wrapper with staleness protection
function depositCollateral(
address _collateralAddress,
uint256 _collateralAmount
) public moreThanZero(_collateralAmount) nonReentrant isAllowedToken(_collateralAddress) {
// Track user's collateral deposit
s_collateralDeposited[msg.sender][_collateralAddress] += _collateralAmount;
emit CollateralDeposited(msg.sender, _collateralAddress, _collateralAmount);
// Transfer tokens from user to this contract
bool success = IERC20(_collateralAddress).transferFrom(
msg.sender,
address(this),
_collateralAmount
);
if (!success) revert DSCEngine__TransferFailed();
}
function mintDSC(uint256 _amountOfDSCToMint) public moreThanZero(_amountOfDSCToMint) nonReentrant {
// Increase user's debt
s_DSCMinted[msg.sender] += _amountOfDSCToMint;
// Check health factor - MUST stay >= 1.0
_revertIfHealthFactorIsBroken(msg.sender);
// Mint DSC tokens to user
bool success = i_dsc.mint(msg.sender, _amountOfDSCToMint);
if (!success) revert DSCEngine__MintFailed();
}
function liquidate(
address _tokenCollateralAddress,
address _user,
uint256 _debtToCover
) public moreThanZero(_debtToCover) nonReentrant {
// 1. Check target is liquidatable
uint256 startingHealthFactor = _healthFactor(_user);
if (startingHealthFactor >= MIN_HEALTH_FACTOR) {
revert DSCEngine__HealthFactorIsNotBroken(startingHealthFactor);
}
// 2. Calculate collateral to seize (debt value + 10% bonus)
uint256 tokenAmount = getTokenAmountFromUsd(_tokenCollateralAddress, _debtToCover);
uint256 bonus = (tokenAmount * LIQUIDATION_BONUS) / LIQUIDATION_PRECISION;
// 3. Transfer collateral to liquidator, burn their DSC
_redeemCollateral(_user, msg.sender, _tokenCollateralAddress, tokenAmount + bonus);
_burnDSC(_debtToCover, _user, msg.sender);
// 4. Verify liquidation improved health
if (_healthFactor(_user) <= startingHealthFactor) {
revert DSCEngine__HealthFactorNotImproved();
}
}
Fuzz tests use random inputs to find edge cases. Foundry runs these 256+ times with different values.
// OracleStalePriceFuzz.t.sol
function testFuzzGetUsdValueRevertsOnStalePrice(
uint256 _collateralSeed,
uint256 _secondsStale
) public {
// Bound stale duration: > 3 hours, max 30 days
_secondsStale = bound(_secondsStale, TIMEOUT + 1, 30 days);
// Make oracle stale
priceFeed.updateRoundData(1, price, block.timestamp - _secondsStale, ...);
// Should revert with OracleLib__StalePrice
vm.expectRevert(OracleLib.OracleLib__StalePrice.selector);
dsCEngine.getUSDValue(collateral, 1e18);
}
Invariant tests ensure protocol rules NEVER break, regardless of what operations are performed.
Total Collateral Value (USD) >= Total DSC Supply
All getter functions should NEVER revert
0x47AE250261B6142a9Eede69F17FA11DF3d11D5e6
0x93d8535A43bC44b28441A42945e14f1CD5871831
0x165a43AF20746074D4dbe554AB02a77e0B3892ac
# 1. Set up .env file
PRIVATE_KEY=0xyour_private_key
ALCHEMY=https://eth-sepolia.g.alchemy.com/v2/your_key
ETHERSCAN=your_etherscan_api_key
# 2. Deploy & Verify
source .env && forge script script/DeployDSCEngine.s.sol \
--rpc-url $ALCHEMY \
--broadcast \
--verify \
--etherscan-api-key $ETHERSCAN