Author: Saurav Kumar
Tags:solidity,defi,stablecoin,ethereum,blockchain
Difficulty: Intermediate โ Advanced
Reading Time: ~10 minutes
๐ก Introduction
In decentralized finance (DeFi), stablecoins play a crucial role in bridging traditional finance and crypto.
They provide price stability, liquidity, and on-chain utilityโacting as the backbone of protocols like MakerDAO (DAI), Aave, and Curve.
In this guide, weโll build a collateral-backed stablecoin named StableUSD (sUSD) using Solidity and Foundry, demonstrating how to maintain a 1:1 peg to the US dollar through collateralization and oracle-based pricing.
This article is part of my DeFi Engineering Series, where I recreate real-world protocols from scratch.
๐ง What Youโll Learn
- How stablecoins maintain their price peg
- How to design collateralized minting and redemption flows
- How to integrate oracles (mock or Chainlink-style)
- How to implement collateralization ratios and burn/mint mechanics
- How to test everything using Foundry
๐ Project File Structure
Hereโs the Foundry project layout:
day-29-stablecoin/
โโ foundry.toml
โโ .gitignore
โโ script/
โ โโ Deploy.s.sol
โโ src/
โ โโ StableUSD.sol
โ โโ OracleManager.sol
โ โโ CollateralPool.sol
โ โโ Treasury.sol
โ โโ MockOracle.sol
โ โโ MockERC20.sol
โ โโ interfaces/
โ โโ IAggregatorV3.sol
โโ test/
โโ Stablecoin.t.sol
๐งฉ System Overview
| Component | Description |
|---|---|
| StableUSD.sol | ERC20 token for sUSD, mint/burn controlled |
| OracleManager.sol | Connects to Chainlink or mock price feeds |
| CollateralPool.sol | Core logic for deposits, minting & redemption |
| Treasury.sol | Admin contract for reserves and fee collection |
๐๏ธ Architecture Diagram
User โ CollateralPool โ StableUSD
โ
OracleManager
โ
Treasury
- Users deposit collateral (e.g., WETH).
- OracleManager provides price data in USD.
-
CollateralPool mints
sUSDtokens based on collateral value. - Treasury manages reserves and stability actions.
โ๏ธ Setup Commands
forge init day-29-stablecoin
cd day-29-stablecoin
forge install OpenZeppelin/openzeppelin-contracts
forge install foundry-rs/forge-std
๐งฉ Smart Contracts
๐ src/interfaces/IAggregatorV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IAggregatorV3 {
function latestRoundData()
external
view
returns (
uint80,
int256 answer,
uint256,
uint256,
uint80
);
}
๐ src/StableUSD.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract StableUSD is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() ERC20("Stable USD", "sUSD") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyRole(MINTER_ROLE) {
_burn(from, amount);
}
}
โ
Implements ERC20
โ
Controlled mint/burn via roles
๐ src/OracleManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./interfaces/IAggregatorV3.sol";
contract OracleManager {
IAggregatorV3 public oracle;
constructor(address _oracle) {
oracle = IAggregatorV3(_oracle);
}
function getLatestPrice() external view returns (uint256) {
(, int256 answer,,,) = oracle.latestRoundData();
require(answer > 0, "Invalid price");
return uint256(answer);
}
}
๐ก Use Chainlink price feeds for live ETH/USD in production.
๐ src/MockOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MockOracle {
int256 private price;
constructor(int256 _price) {
price = _price;
}
function latestRoundData()
external
view
returns (uint80, int256, uint256, uint256, uint80)
{
return (0, price, 0, 0, 0);
}
function updatePrice(int256 _price) external {
price = _price;
}
}
โ Simulates Chainlink oracles for local testing
๐ src/MockERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
โ Mock collateral asset (like WETH) for testing mint/redeem logic
๐ src/CollateralPool.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./StableUSD.sol";
import "./OracleManager.sol";
contract CollateralPool {
IERC20 public collateral;
StableUSD public stable;
OracleManager public oracle;
uint256 public constant COLLATERAL_RATIO = 150; // 150%
mapping(address => uint256) public collateralBalance;
constructor(IERC20 _collateral, StableUSD _stable, OracleManager _oracle) {
collateral = _collateral;
stable = _stable;
oracle = _oracle;
}
function deposit(uint256 amount) external {
require(amount > 0, "Invalid amount");
collateral.transferFrom(msg.sender, address(this), amount);
collateralBalance[msg.sender] += amount;
}
function mint(uint256 amountCollateral) external {
require(collateralBalance[msg.sender] >= amountCollateral, "Insufficient collateral");
uint256 price = oracle.getLatestPrice();
uint256 usdValue = (amountCollateral * price) / 1e8;
uint256 mintAmount = (usdValue * 100) / COLLATERAL_RATIO;
stable.mint(msg.sender, mintAmount * 1e18);
}
function redeem(uint256 sUSDAmount) external {
stable.burn(msg.sender, sUSDAmount);
uint256 price = oracle.getLatestPrice();
uint256 collateralToReturn = (sUSDAmount * 1e8 * COLLATERAL_RATIO) / (price * 100);
require(collateralBalance[msg.sender] >= collateralToReturn, "Not enough collateral");
collateralBalance[msg.sender] -= collateralToReturn;
collateral.transfer(msg.sender, collateralToReturn);
}
}
โก Implements over-collateralized mint/redeem flow
โก Maintains peg using oracle price
โก 150% ratio ensures safety
๐ src/Treasury.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Treasury {
address public owner;
constructor() {
owner = msg.sender;
}
function withdraw(address token, address to, uint256 amount) external {
require(msg.sender == owner, "Not authorized");
(bool ok,) = token.call(abi.encodeWithSignature("transfer(address,uint256)", to, amount));
require(ok, "Transfer failed");
}
}
โ Simple treasury for system fund management
๐ script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/StableUSD.sol";
import "../src/OracleManager.sol";
import "../src/CollateralPool.sol";
import "../src/MockERC20.sol";
import "../src/MockOracle.sol";
contract Deploy is Script {
function run() external {
vm.startBroadcast();
MockERC20 collateral = new MockERC20("Wrapped ETH", "WETH");
MockOracle oracle = new MockOracle(1800e8);
StableUSD stable = new StableUSD();
OracleManager oracleManager = new OracleManager(address(oracle));
CollateralPool pool = new CollateralPool(collateral, stable, oracleManager);
stable.grantRole(stable.MINTER_ROLE(), address(pool));
vm.stopBroadcast();
}
}
โ Deploys all components in one transaction
๐ test/Stablecoin.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/StableUSD.sol";
import "../src/CollateralPool.sol";
import "../src/OracleManager.sol";
import "../src/MockERC20.sol";
import "../src/MockOracle.sol";
contract StablecoinTest is Test {
StableUSD stable;
OracleManager oracle;
CollateralPool pool;
MockERC20 collateral;
function setUp() public {
collateral = new MockERC20("Wrapped ETH", "WETH");
stable = new StableUSD();
oracle = new OracleManager(address(new MockOracle(1800e8)));
pool = new CollateralPool(IERC20(address(collateral)), stable, oracle);
stable.grantRole(stable.MINTER_ROLE(), address(pool));
}
function testMintStablecoin() public {
collateral.mint(address(this), 1 ether);
collateral.approve(address(pool), 1 ether);
pool.deposit(1 ether);
pool.mint(1 ether);
assertGt(stable.balanceOf(address(this)), 0);
}
}
โ
Tests deposit โ mint flow
โ
Confirms minting works using oracle-based pricing
๐งช Test & Run
forge build
forge test -vv
๐งฎ Peg Example
If 1 ETH = $1800 and collateral ratio = 150%,
then 1 ETH mints:
(1800 * 100 / 150) = 1200 sUSD
๐ง Key Learnings
โ
How stablecoins maintain a peg
โ
Using oracles to stabilize token value
โ
Over-collateralization mechanics
โ
Writing and testing DeFi smart contracts
๐ก๏ธ Security & Design Notes
- Always use verified Chainlink oracles in production.
- Implement liquidation logic for under-collateralized users.
- Add pause and governance controls for protocol safety.
- Track debt positions per user in a full system.
๐ Next Steps
- Multi-collateral vaults (ETH, USDC, WBTC)
- Dynamic interest and stability fees
- DAO-based parameter governance
- Integration with Uniswap for peg stabilization
๐งญ Conclusion
Stablecoins are the core monetary layer of DeFi โ building one from scratch deepens your understanding of price oracles, collateralization, and supply control.
This StableUSD demo shows how trustless monetary systems can maintain stability purely through code and math.

