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.
Top comments (0)