30 分钟搭建可升级的 ETH 超额抵押稳定币系统:OpenZeppelin + Chainlink 实战指南

前言

基于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 场景。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容