This repository implements a fully on-chain, exogenously collateralized stable coin inspired by MakerDAO but trimmed down to the essentials. Users lock trusted collateral (WETH/WBTC) inside DSCEngine to mint DecentralizedStableCoin (DSC) at a soft USD peg. Health factors, on-chain pricing via Chainlink oracles, and a liquidation bonus keep the system overcollateralized at all times.
- Permissionless collateralized minting – deposit approved collateral and mint DSC in a single call.
- Chainlink-driven pricing – WETH/WBTC collateral is priced with AggregatorV3 feeds (mocked automatically for local Anvil runs).
- Deterministic health checks – accounts must stay ≥200% overcollateralized (health factor ≥ 1); otherwise anyone can liquidate for a 10% bonus.
- Minimal governance surface – no protocol token, fees, or owner-only knobs beyond what the contracts enforce.
- Comprehensive test suite – unit, fuzz, and invariant tests ensure collateral value always covers DSC supply.
| Component | Description |
|---|---|
src/DecentralizedStableCoin.sol |
ERC20-burnable token. Minting/burning restricted to DSCEngine. |
src/DSCEngine.sol |
Core logic for depositing collateral, minting/burning DSC, redemptions, health-factor checks, and liquidations. |
script/HelperConfig.s.sol |
Supplies network-specific config. Deploys ERC20/price feed mocks when running locally; returns real addresses on Sepolia. |
script/DeployScript.s.sol |
Single deployment entry point for DSC + DSCEngine. Sets DSCEngine as DSC owner. |
test/* |
Unit (test/unit), fuzz (test/fuzz), and supporting mocks (test/mocks). Invariants assert total collateral value ≥ DSC supply. |
Approved collateral & oracle pairs live in HelperConfig. By default the protocol supports:
- Local/Anvil – Mock WETH/WBTC ERC20s plus mock 8-decimal price feeds seeded at 2000 USD (ETH) and 1000 USD (BTC).
- Sepolia – Real Chainlink feeds (
0x694A…5306for ETH/USD,0x1b44…1Ee43for BTC/USD) and canonical testnet WETH/WBTC wrappers.
To add collateral, extend the constructor arrays in DSCEngine and mirror the addresses & feeds in HelperConfig.
src/– Smart contracts.script/– Deployment helpers (Foundryforge script).test/unit– Deterministic DSCEngine unit tests.test/fuzz– Handler-driven fuzzing + invariant checks.test/mocks– Chainlink aggregators and ERC20 mocks for local networks.broadcast/– Forge broadcast artifacts (populated after deployments).
- Foundry (
foundryup,forge,cast,anvil). - Node 18+/pnpm (optional) if you script anything around the contracts.
- Access to the desired RPC endpoints (Anvil, Sepolia, mainnet fork, etc.).
Clone the repo and install dependencies:
git clone <repo-url>
cd defi
forge install
forge build# All tests (unit + fuzz + invariants)
forge test
# Only unit tests
forge test --match-path test/unit/DSCEngineTest.t.sol
# Only invariant suite (expensive)
forge test --match-contract InvariantsTest -vvvv
foundry.tomlalready configures invariant runs/depth. Adjust[invariant]settings if you need more aggressive fuzzing.
- Start a local chain:
anvil --accounts 10 --mnemonic "test test test test test test test test test test test junk". - Export the corresponding private key (first account by default):
export DEPLOYER_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - Deploy both contracts:
forge script script/DeployScript.s.sol:DeployScript \ --rpc-url http://127.0.0.1:8545 \ --private-key $DEPLOYER_KEY \ --broadcast -vvvv
HelperConfig detects the non-Sepolia chain ID, deploys mock feeds/ERC20s, and wires them into the DSCEngine constructor for you.
Set your RPC URL and funded deployer key (never commit private keys):
export SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/<id>
export PRIVATE_KEY=0xabc123...
forge script script/DeployScript.s.sol:DeployScript \
--rpc-url $SEPOLIA_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast --verify -vvvvThe Sepolia config uses real WETH/WBTC addresses and Chainlink feeds. Update HelperConfig.getSepoliaEthConfig() if you want to deploy with a different collateral set or a hardware wallet signer.
graph LR
A[Deposit WETH/WBTC] --> B[Mint DSC]
B --> C[Use DSC - trade/LP]
C --> D[Burn DSC]
D --> E[Redeem Collateral]
A --> F[Liquidation Monitor]
F -->|Health factor < 1| G[Liquidate & Earn 10% Bonus]
Example CLI sequence after deployment:
# Approve DSCEngine to pull your collateral
cast send <WETH_ADDRESS> "approve(address,uint256)" <DSC_ENGINE_ADDRESS> 5ether \
--rpc-url $RPC --private-key $PK
# Deposit collateral + mint DSC
cast send <DSC_ENGINE_ADDRESS> \
"depositCollateralAndMintDSC(address,uint256,uint256)" <WETH_ADDRESS> 5ether 2000e18 \
--rpc-url $RPC --private-key $PK
# Repay and redeem
cast send <DSC_ADDRESS> "approve(address,uint256)" <DSC_ENGINE_ADDRESS> 2000e18 ...
cast send <DSC_ENGINE_ADDRESS> "redeemCollateralForDsc(address,uint256,uint256)" <WETH_ADDRESS> 5ether 2000e18 ...Other useful entrypoints:
mintDsc(uint256)– mint after collateral is already deposited.redeemCollateral(address,uint256)– withdraw collateral (subject to health factor).burnDsc(uint256)– repay debt without redeeming.liquidate(address,address,uint256)– burn DSC to liquidate unhealthy positions and claim collateral + bonus.
- Add collateral – Update the arrays in
DSCEngine’s constructor call (seescript/DeployScript.s.sol) and surface the feed/token pair inHelperConfig. - Tweak risk parameters – Constants such as
LIQUIDATION_THRESHOLD,LIQUIDATION_BONUS, andMIN_HEALTH_FACTORgovern leverage and incentives. - Integrate UI/bots – Monitor
_healthFactorvia the public view helpers to trigger automatic liquidations or dashboards.
- Educational codebase; it has not been audited and should not secure real funds.
- Only WETH/WBTC collateral is wired up. You must extend the config files before supporting other tokens.
HelperConfigcurrently stores a deployer key in plain text for convenience. Replace it with environment variables or hardware signer integrations for production use.
Questions or improvements? Open an issue or start a discussion—contributions are always welcome.