前言
基于openzeppelin库实现一个质押稳定币,主要包含稳定币合约、保险合约、以及chailink喂价合约;
稳定币和ERC20关联关系
一句话总结:ERC20 只是通用代币身份证,稳定币是“必须保持价格锚定”的 ERC20 代币
维度 | 稳定币(举例:USDC、DAI) | 普通 ERC20(举例:UNI、APE) |
---|---|---|
接口层 | ✅ 100 % 符合 ERC20 | ✅ 100 % 符合 ERC20 |
价格目标 | 锚定 1 美元(或 1 克黄金、1 CNY 等) | 市场自由浮动 |
价值支撑 | 必须有链下/链上储备或算法调节(见下表) | 取决于项目基本面、投机需求 |
额外合约逻辑 | 清算、铸币/销毁、喂价、KYC 黑名单、升级代理 | 通常只有转账 + 授权 |
稳定币类型
类型 | 机制 | 优点 | 缺点 | 案例 |
---|---|---|---|---|
法币抵押型 | 1:1 储备美元等法币 | 简单、稳定 | 中心化、需审计 | USDC、USDT |
加密资产抵押型 | 超额抵押 ETH 等链上资产 | 去中心化、透明 | 复杂、需清算机制 | DAI、sUSD |
算法型 | 通过智能合约调节供需 | 无需抵押、资本效率高 | 易受市场恐慌影响 | UST(失败案例) |
核心应用场景
场景 | 说明 |
---|---|
加密交易媒介 | 现货交易 |
DeFi 基础设施 | 作为借贷、AMM、收益农场的基础资产 |
跨境支付 / 汇款 | 国际贸易结算 |
价值存储与避险 | 市场波动期快速换成稳定币,降低资产回撤 |
RWA(现实世界资产)交易 | 代表现实资产(债券、房产)的代币以稳定币计价,提升链上流动性 |
开发合约
-
稳定币合约
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.27;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract USDStableCoin is ERC20, ERC20Burnable, Ownable, ERC20Permit {
constructor(address initialOwner)
ERC20("MyStablecoin", "MST")
Ownable(initialOwner)
ERC20Permit("MyStablecoin")
{}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
-
chainlink喂价合约
- 第一种
// SPDX-License-Identifier: MIT pragma solidity ^0.8.27; import "@chainlink/contracts/src/v0.8/tests/MockV3Aggregator.sol";
- 第二种
// SPDX-License-Identifier: MIT pragma solidity ^0.8.27; interface AggregatorV3Interface { function decimals() external view returns (uint8); function description() external view returns (string memory); function version() external pure returns (uint256); function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ); } contract MockV3Aggregator is AggregatorV3Interface { uint8 public decimals; string public description; int256 private s_answer; constructor(uint8 _d, string memory _desc, int256 _initial) { decimals = _d; description = _desc; s_answer = _initial; } /* ===== 实现接口 ===== */ function version() external pure override returns (uint256) { return 1; } function latestRoundData() external view override returns ( uint80,int256,uint256,uint256,uint80 ) { return (0, s_answer, 0, block.timestamp, 0); } }
- 第三种
直接使用chainlink部署现成的合约地址: 例如:sepolia链上: 0x694AA1769357215DE4FAC081bf1f309aDC325306; // Sepolia ETH/USD
-
特别说明:
部署前两种时,要部署MockV3Aggregator否则会报警告:这个合约可能是抽象的,它可能没有完全实现抽象父类的方法,或者它可能没有正确调用继承合约的构造函数。部署时根据构造函数的参数填写
-
保险库合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./USDStableCoin.sol";
/*
* 功能极简版:
* 1. 允许用户超额抵押 ETH 铸造 USC;
* 2. 抵押率 150%;
* 3. 价格喂价使用 Chainlink Sepolia ETH/USD
*/
contract Vault is Ownable {
USDStableCoin public immutable usc;
AggregatorV3Interface internal priceFeed;
uint256 public constant COLLATERAL_RATIO = 150; // 150%
uint8 public constant FEED_DECIMALS = 8;
mapping(address => uint256) public collateral;
constructor(address _usc, address _priceFeed) Ownable(msg.sender) {
usc = USDStableCoin(_usc);
priceFeed = AggregatorV3Interface(_priceFeed);
}
/* ========== 用户接口 ========== */
function depositAndMint() external payable {
require(msg.value > 0, "No ETH");
collateral[msg.sender] += msg.value;
uint256 mintAmount = (getEthUsd(msg.value) * 100) / COLLATERAL_RATIO;
usc.mint(msg.sender, mintAmount);
}
function repayAndWithdraw(uint256 uscAmount, uint256 ethAmount) external {
usc.burn(uscAmount);
uint256 neededUsd = (ethAmount * getEthUsd(1 ether)) / 1e18;
require(neededUsd <= uscAmount, "Under repay");
collateral[msg.sender] -= ethAmount;
payable(msg.sender).transfer(ethAmount);
}
/* ========== 内部函数 ========== */
function getEthUsd(uint256 weiAmount) public view returns (uint256) {
(, int256 price,,,) = priceFeed.latestRoundData(); // 8 位小数
require(price > 0, "Bad price");
return (weiAmount * uint256(price)) / 10 ** FEED_DECIMALS;
}
receive() external payable {}
}
测试
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Vault & USDStableCoin Integration", function () {
let usc, vault, mockV3;
let owner, alice;
const DECIMALS = 8;
const ETH_USD_PRICE = 2000 * 10 ** DECIMALS; // 2000 USD 8 位小数
beforeEach(async function () {
[owner, alice] = await ethers.getSigners();
/* 1. 部署 USDStableCoin */
const USC = await ethers.getContractFactory("USDStableCoin");
usc = await USC.deploy();
await usc.deployed();
/* 2. 部署 MockV3Aggregator */
const Mock = await ethers.getContractFactory("MockV3Aggregator");
mockV3 = await Mock.deploy(DECIMALS, "ETH / USD", ETH_USD_PRICE);
await mockV3.deployed();
/* 3. 部署 Vault */
const Vault = await ethers.getContractFactory("Vault");
vault = await Vault.deploy(usc.address, mockV3.address);
await vault.deployed();
/* 4. 把铸币权限转给 vault */
await usc.transferOwnership(vault.address);
});
describe("depositAndMint", function () {
it("should mint USC after depositing ETH", async function () {
const ethDeposit = ethers.utils.parseEther("1"); // 1 ETH
await vault.connect(alice).depositAndMint({ value: ethDeposit });
const expected = ethDeposit.mul(ETH_USD_PRICE).div(10 ** DECIMALS).mul(100).div(150);
expect(await usc.balanceOf(alice.address)).to.eq(expected);
});
it("should revert if deposit 0 ETH", async function () {
await expect(vault.connect(alice).depositAndMint({ value: 0 }))
.to.be.revertedWith("No ETH");
});
});
describe("repayAndWithdraw", function () {
beforeEach(async function () {
await vault.connect(alice).depositAndMint({ value: ethers.utils.parseEther("1") });
});
it("should redeem ETH after burning USC", async function () {
const uscBalance = await usc.balanceOf(alice.address);
const ethToWithdraw = ethers.utils.parseEther("0.5");
const neededUsd = ethToWithdraw.mul(ETH_USD_PRICE).div(ethers.utils.parseEther("1"));
const burnAmount = neededUsd.add(1); // 多烧 1 wei 避免精度问题
await usc.connect(alice).approve(vault.address, burnAmount);
const tx = await vault.connect(alice).repayAndWithdraw(burnAmount, ethToWithdraw);
await expect(tx).to.changeEtherBalance(alice, ethToWithdraw);
expect(await usc.balanceOf(alice.address)).to.lt(uscBalance);
});
it("should revert if repay amount < needed USD", async function () {
const ethToWithdraw = ethers.utils.parseEther("1");
const uscBalance = await usc.balanceOf(alice.address);
await usc.connect(alice).approve(vault.address, uscBalance);
await expect(
vault.connect(alice).repayAndWithdraw(uscBalance, ethToWithdraw)
).to.be.revertedWith("Under repay");
});
});
});
部署
MockV3Aggregator 合约部署
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const getNamedAccount = (await getNamedAccounts()).firstAccount;
const MockV3Aggregator=await deploy("MockV3Aggregator", {
from: getNamedAccount,
args: [], // 传递的参数
log: true,
});
console.log("MockV3Aggregator deployed at:", MockV3Aggregator.address);
};
// 部署治理 npx hardhat deploy --tags Token 指定的部署文件 部署的文件包是Deploy
module.exports.tags = ["all", "MockV3Aggregator"];
USDStableCoin 合约部署:同上
Vault 合约部署
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const getNamedAccount = (await getNamedAccounts()).firstAccount;
const MockV3Aggregator=await deployments.get("MockV3Aggregator");//读取MockV3Aggregator合约地址
const USDStableCoin=await deployments.get("USDStableCoin");//读取USDStableCoin合约地址
const Vault=await deploy("Vault", {
from: getNamedAccount,
args: [USDStableCoin.address,MockV3Aggregator.MockV3Aggregator], // 传递的参数
log: true,
});
console.log("Vault deployed at:", Vault.address);
};
// 部署治理 npx hardhat deploy --tags Token 指定的部署文件 部署的文件包是Deploy
module.exports.tags = ["all", "Vault"];
总结
本文用 OpenZeppelin 快速拼装了一个可超额抵押 ETH 生成链上稳定币的完整最小系统:
- 稳定币(ERC20 + 铸币/销毁)
- 保险库(150 % 抵押率,Chainlink 喂价)
- 测试(Hardhat + MockV3Aggregator)
- 部署(hardhat-deploy 脚本一键上链)
开发者拿到代码即可在本地/Sepolia 三分钟跑通“存 ETH → 领稳定币 → 赎回”全流程,并基于这套骨架继续扩展清算、升级、RWA 等复杂 DeFi 场景。