Skip to content

Repository files navigation

Exogenously-collateralized USD stablecoin (DSC)

1 DSC tracks 1 USD, backed by WETH and WBTC at a 200% collateral ratio with a 10% liquidation bonus. Algorithmically stabilized — no governance, no fees, no protocol-owned treasury. Foundry unit + paired invariant suites, Slither in CI, Sepolia deployment with verified contracts.

Test Slither

Closer in spirit to single-purpose MakerDAO without DAI's surface area.

Protocol

Property Choice
Peg USD (1 DSC = $1)
Collateral Exogenous (crypto) — WETH (18 dec), WBTC (8 dec)
Stability mechanism Minting / burning, algorithmically decentralized — users may only mint with enough collateral
Price feeds Chainlink, staleness-checked, with on-chain ETH/BTC ↔ USD conversion helpers
Relative stability Anchored / pegged to the US Dollar
Collateral ratio 200%
Liquidation bonus 10%

Users deposit collateral and mint DSC against it. If a position's collateral value falls below 200% of its debt, other users may liquidate it by burning DSC on the user's behalf and seizing the same USD value in collateral plus an extra 10% as a bonus — this bonus is the only incentive keeping the currency collateralized, since there is no automated keeper. Self-liquidation is permitted, and a user who liquidates herself does not lose the 10% bonus to a third party.

Architecture

flowchart LR
    User([User / Liquidator])
    Engine[DSCEngine]
    DSC[DecentralizedStableCoin<br/>ERC20]
    Oracle[OracleLib]
    Chainlink[(Chainlink<br/>ETH/USD · BTC/USD)]

    User -->|deposit · mint · burn · redeem · liquidate| Engine
    Engine -->|owns · sole minter| DSC
    Engine -.->|using OracleLib for AggregatorV3Interface| Oracle
    Oracle -->|staleCheckLatestRoundData| Chainlink
    Oracle -. revert on stale / zero / backwards round .-> Engine
Loading

Three on-chain contracts in a single ownership chain:

  • DSCEngine — entry point for deposit, mint, burn, redeem, liquidate. Custodies collateral, tracks per-user debt, computes the health factor that gates every state-changing call. Exposes getUsdValue and getTokenAmountFromUsd as the ETH/BTC ↔ USD conversion surface.
  • DecentralizedStableCoin — the ERC20 that DSCEngine mints. Ownership is transferred to the engine atomically during deployment, so the engine is the sole minter for the lifetime of the protocol.
  • OracleLibusing-attached Chainlink wrapper. Reverts on stale or backwards round data, intentionally freezing the protocol rather than transacting on degraded prices.

Design notes

  • State-before-transfer ordering. Every public entry point updates storage, then performs the ERC20 call, then closes with a health-factor check on the caller. The private helpers (_redeemCollateral, _burnDsc) exist specifically to enforce this ordering at the public boundary.
  • Multi-decimal collateral on one math path. WBTC (8 dec) and WETH (18 dec) share the engine's valuation logic via per-token decimal scaling applied at deposit accounting and at oracle normalization. Tokens with more than 18 decimals are rejected at construction.
  • Oracle freeze-on-stale. A single library wraps every Chainlink read. A stale, zero, or out-of-order round halts every priced operation — the protocol prefers freezing to serving wrong prices.
  • Failure-path mocks. A MockDSC* family deliberately returns false from mint / transfer / transferFrom, and one variant crashes the oracle mid-burn. These reach the engine's defensive if (!success) revert and oracle-collapse branches that would otherwise be untestable.
  • Two-flavor invariant fuzzing. A strict FailOnRevertHandler (every bounded call must succeed) and a lenient ContinueOnRevertHandler (broader state exploration with reverts tolerated) drive the same two solvency invariants:
    1. Total collateral USD value ≥ DSC total supply.
    2. Σ per-user dscMinted equals totalSupply.
  • Slither in CI. Configured to fail the build on high findings, with inline // slither-disable-next-line suppressions justified one comment above the disabled line.
  • Chain-keyed deploy + auto-discovering scripts. HelperConfig switches between Anvil mocks and Sepolia's live Chainlink feeds by block.chainid. Per-action interaction scripts resolve the latest deployed addresses via DevOpsTools, so re-deploying and then calling any action "just works" without passing addresses around.

Testing

forge test runs three tiers under test/:

  • unit/ — happy paths and revert-expecting paths, including the failure-path mock suite.
  • invariant/ — paired strict + lenient runners against a stateful handler that tracks fuzzed users in an EnumerableSet.
  • Sepolia and mainnet fork variants of the unit suite via dedicated Makefile targets.

Line coverage on src/ is 100%, measured via make coverage (forge coverage scoped to production sources, invariant runs excluded).

CI runs forge fmt --check, forge build --sizes, the unit suite, the invariant suite, and Slither on every pull request (and on pushes to main/release/develop).

Deployment on Sepolia

Contracts

Interactions

Quickstart

make install         # forge soldeer install
make build
make test            # unit + invariant
make slither
make deploy-sepolia

See the Makefile for the full target list (per-action interaction scripts, coverage, fork tests).

Provenance

Builds on the Cyfrin Advanced Foundry teaching codebase by Patrick Collins / Cyfrin (repo, CodeHawks audit), with adaptations (OpenZeppelin 5.x, solc 0.8.27, Slither hygiene) and one substantive extension: per-token decimal scaling so 8-dec WBTC and 18-dec WETH share one valuation path. The invariant suite, the failure-path mock family, and the Sepolia deployment are additions on top of the upstream contracts.

About

USD-pegged ERC20 stablecoin in Solidity/Foundry. 200% over-collateralized with WETH/WBTC, Chainlink-priced, invariant-fuzzed, Slither-gated in CI, deployed on Sepolia.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages