前言基于openzeppelin库实现一个质押稳定币,主要包含稳定币合约、保险合约、以及chailink喂价合约;稳定币和ERC20关联关系一句话总结:ERC20只是通用代币身份证,稳定币是“必须保持价格锚定”的ERC20代币
基于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";
/*
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 场景。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!