如何使用Foundry创建一种超抵押稳定币

  • QuickNode
  • 发布于 2025-02-24 22:13
  • 阅读 131

本文指南提供了如何创建和部署自己的超额抵押稳定币的详细步骤,使用Foundry作为智能合约开发工具,结合OpenZeppelin和Chainlink。内容涵盖了合约的核心功能、抵押品管理、清算机制及相关测试,适合有基本Solidity和智能合约知识的开发者。

警告

本指南中的代码仅用于教育和演示目的。它是非生产就绪的,不应在实时环境中使用。

概述

虽然以 USDC 和 USDT 为代表的中心化稳定币在市场上占据主导地位(数据源:EthereumSolana),并且今天在区块链交易量中占有重要份额,但对去中心化替代品的需求持续增长。本指南将引导你使用 Foundry v1.0 作为我们的智能合约开发工具包,OpenZeppelin 为标准化的代币合约,以及 Chainlink 作为我们的价格预言机,创建、测试、部署和与你自己的超抵押稳定币进行交互。

让我们开始吧!

你将做什么

  • 学习关于 Foundry 的知识
  • 回顾 Foundry v1.0 中的更改及迁移提示
  • 创建一个 QuickNode 端点 + 资助你的钱包(从 这里 获取一些测试资金!)
  • 编写、测试、部署和与稳定币智能合约进行交互(兼容 Foundry v1.0)

你将需要什么

  • 对 Solidity 和智能合约的基本理解
  • 安装 Foundry v1.0(最新版本)
  • 一个代码编辑器(推荐 VSCode)
  • 终端/命令行界面
  • 安装 Git
  • Node.js(最新的 LTS 版本)

Foundry

Foundry 是一个用于在 EVM 区块链上构建和部署智能合约的开发框架。它旨在使各级开发者更容易创建和部署安全高效的智能合约。

最近,Foundry 发布了 v1.0,它代表了以太坊开发工具的一个重要里程碑,为我们构建和测试智能合约的方法带来了更多的效率和稳定性。

Foundry v1.0 迁移提示

提示

如果你是 Foundry 的新手,可以跳过此部分,因为它与你将在未来进行的智能合约开发工作无关。

Foundry v1.0 对我们开发智能合约的方式进行了更改。主要更改包括:

  • Solc 优化器更改:默认情况下禁用优化器以防止潜在的优化错误。这意味着你需要在 foundry.toml 中显式启用它。

  • 测试框架更新

    • 移除了对 testFail 前缀的支持(请使用 vm.expectRevert()
    • 更改了对内部调用的 expectRevert 的行为
    • 新的断言方法和测试工具
  • 重新映射和依赖性

    • 更严格处理冲突的重新映射
    • 管理项目依赖关系的新方法
    • 更明确的源文件处理

你无需担心在本指南中迁移代码,因为所有代码都将符合 v1.0。要了解更多信息,请查看 Foundry 的这篇迁移 文章

项目前提条件:创建 QuickNode 端点

要与区块链进行通信,你需要访问节点。虽然我们可以运行自己的节点,但在 QuickNode,我们使快速启动区块链节点变得简单易行。你可以在 这里注册账户

登录后,创建以太坊 Sepolia 测试网区块链的端点。保存 HTTP URL,它应该看起来像这样:

Quicknode 主网端点的屏幕截图

提示

本指南是 EVM 兼容的。如果你想在其他链上部署代币,选择其他 EVM 兼容链(例如:Optimism、Arbitrum 等),并相应更新钱包和 RPC URL。你还可以将 Chain Prism 附加组件添加到你的端点,以便在同一端点内访问多个区块链 RPC URL。

项目前提条件:从 QuickNode 多链水龙头获取 ETH

为了在链上进行活动,你需要 ETH 来支付交易费用。由于我们使用的是 Sepolia 测试网,我们可以从 多链 QuickNode 水龙头 获取一些测试 ETH。

导航到 多链 QuickNode 水龙头 连接你的钱包(例如:MetaMask、Coinbase Wallet),或者粘贴你的钱包地址以提取测试 ETH。请注意,要使用 EVM 水龙头,以太坊主网需要维持 0.001 ETH 的主网余额要求。你还可以通过推特或使用你的 QuickNode 账户登录以获得奖励!

QuickNode 水龙头

稳定币类型和架构

稳定币已经从简单的法币支持代币,例如 USDT(2014),演变为更复杂的机制,例如 FRAX(基于算法的)。虽然 USDC 和 USDT 在市场份额中仍占主导地位(数据源:EthereumSolana),去中心化替代品如 DAI(2017;现在为 USDS)开创了加密抵押的新天地。值得注意的是,其他基于算法的实验,如 UST(2020),在 2022 年崩溃后展示了未经检验的稳定机制的风险。

稳定币通常通过抵押(如 USDC 的 1:1 美元支持)、超抵押(如 DAI 的 150% ETH 支持)或算法(例如 FRAX)方法保持其挂钩。每种方法在去中心化、资本效率和稳定性风险之间进行取舍。

现在,让我们回顾一下今天常见的稳定币类型。

超抵押(DAI 模型)

  • 每 1 美元的 DAI 由超过抵押数量的 ETH 支持(即 150%)
  • 市场维持对 1 美元挂钩的信心
  • 如果 ETH 继续下跌,在某个阈值处启动清算
  • 清算者通过销毁 DAI 来购买折扣 ETH
  • 此过程有助于通过以下方式保持挂钩:
    • 在弱势仓位威胁挂钩之前清除它们
    • 在清算期间创造 DAI 买压力
    • 确保系统始终保持超抵押状态

1:1 支持(USDC 模型)

  • 每个 USDC 由 Circle 银行账户中的 1 美元支持
  • 每月证明支持的证明
  • 如果完成 KYC,用户总是可以以 1 美元兑换 1 USDC
  • 当失去支持资产的信心以及银行/储备问题时,可能会发生挂钩问题。例如,在 2023 年 3 月,USDC 在 SVB 危机期间短暂降到 0.87 美元。为什么?市场担心 USDC 的支撑美元可能会在 SVB 中丧失。

基于算法(FRAX 模型)

FRAX 引入了一种混合方法,结合了抵押和算法稳定机制,其中:

  • 部分由流动性支持(USDC、USDT 等)。
  • 部分通过 FRAX 的治理代币 FXS 算法稳定。
  • 抵押比率根据市场条件动态调整。

构建简单的超抵押稳定币

在本指南中,我们将演示如何创建一个简单版本的超抵押稳定币。该稳定币将具有以下特征:

  1. ETH 抵押
  • 接受 ETH 作为抵押,用于 Chainlink 价格预言机
  • 150% 最低抵押比率
  • 130% 清算阈值
  1. 核心功能
  • 通过存入 ETH 进行稳定币铸造
  • 偿还债务以检索抵押品
  • 针对抵押不足仓位的清算系统
  1. 简单的保险库系统
  • 每个用户一个保险库
  • 跟踪抵押品和债务金额
  • 标准 ERC20 实现("简单 USD" - sUSD)

现在让我们进入指南的技术编码部分。

设置项目

现在我们了解了 Foundry v1.0,让我们开始编码。

如果你尚未安装 Foundry,请立即安装。你可以运行 foundryup 命令安装最新稳定版本。

接下来,在你选择的目录中打开终端命令窗口,运行以下命令:

forge init my-foundry-project
cd my-foundry-project

这将创建以下目录结构:

.
├── README.md
├── foundry.toml
├── lib
├── script
├── src
└── test

其中:

  • foundry.toml:作为配置文件
  • lib:存放如 @openzeppelin 的库
  • script:存储脚本,例如智能合约交互、部署等
  • src:智能合约的源代码(例如,使用 Solidity、Yul 编写)
  • test:存储和运行测试(这是最重要的步骤之一!)

让我们安装所需的库依赖项:

forge install foundry-rs/forge-std --no-commit
forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit
forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit
forge install smartcontractkit/chainlink-brownie-contracts@1.1.1 --no-commit

然后,在项目的主目录中创建一个名为 remappings.txt 的文件并设置以下值:

@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/

现在,在我们开始编码之前,让我们明确接下来要构建的内容。

编写稳定币智能合约

信息

本指南中演示的稳定币智能合约不适合生产使用。使用风险自负!强烈建议你在将任何智能合约代码部署到生产环境之前对其进行审计。

现在,让我们开始构建。导航到你的 src 文件夹并创建一个名为 Stablecoin.sol 的文件。

让我们开始添加每个代码块,同时进行解释。

导入

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

我们使用 OpenZeppelin 的智能合约代码,包括 ERC-20 实现和 ReentrancyGuard 以防止重入攻击。我们还导入了 Chainlink 的 AggregatorV3Interface 以获取 ETH/USD 的价格数据。

合约初始化 + 状态变量

在我们的合约初始化(即 contract SimpleStablecoin)中,我们通过从 ERC20ReentrancyGuard 继承,设置基本结构。我们声明 Chainlink 价格预言机接口,并定义用于抵押和清算阈值的核心参数。Vault 结构跟踪每个用户的抵押品和债务位置。

contract SimpleStablecoin is ERC20, ReentrancyGuard {
    AggregatorV3Interface public priceFeed;

    uint256 public constant COLLATERAL_RATIO = 15000; // 150%
    uint256 public constant RATIO_PRECISION = 10000;  // 100%
    uint256 public constant MIN_COLLATERAL = 0.1 ether;
    uint256 public constant LIQUIDATION_THRESHOLD = 13000; // 130%

    struct Vault {
        uint256 collateralAmount;
        uint256 debtAmount;
    }

    mapping(address => Vault) public vaults;

    event VaultUpdated(address indexed user, uint256 collateral, uint256 debt);
    event Liquidated(address indexed user, address indexed liquidator, uint256 debt, uint256 collateralSeized);

    constructor(address _priceFeed) ERC20("Simple USD", "sUSD") {
        priceFeed = AggregatorV3Interface(_priceFeed);
    }
}

从 Chainlink 预言机获取 ETH/USD 价格

getEthPrice 函数查询 Chainlink 的价格预言机以获取最新的 ETH/USD 价格。

// 从 Chainlink 获取 ETH/USD 价格
function getEthPrice() public view returns (uint256) {
    (, int256 price, , , ) = priceFeed.latestRoundData();
    require(price > 0, "无效价格");
    return uint256(price);
}

铸造稳定币

mint 函数允许用户通过存入 ETH 作为抵押来创建新的稳定币。它确保满足最低抵押要求,并计算可以铸造的稳定币的最大安全数量,同时保持所需的抵押比率。

// 通过存入 ETH 铸造稳定币
function mint() external payable nonReentrant {
    require(msg.value >= MIN_COLLATERAL, "低于最低抵押");

    Vault storage vault = vaults[msg.sender];
    uint256 ethPrice = getEthPrice();

    uint256 newCollateral = vault.collateralAmount + msg.value;
    uint256 collateralValue = (newCollateral * ethPrice) / 1e8;
    uint256 maxSafeDebt = (collateralValue * RATIO_PRECISION) / COLLATERAL_RATIO;

    uint256 additionalDebt = maxSafeDebt;
    if (vault.debtAmount > 0) {
        require(maxSafeDebt > vault.debtAmount, "没有额外的债务可用");
        additionalDebt = maxSafeDebt - vault.debtAmount;
    }

    vault.collateralAmount = newCollateral;
    vault.debtAmount += additionalDebt;

    _mint(msg.sender, additionalDebt);

    emit VaultUpdated(msg.sender, newCollateral, vault.debtAmount);
}

偿还稳定币债务

repay 函数允许用户偿还其债务并检索其抵押品。用户可以进行部分还款,如果他们偿还了所有债务,将返回其全部抵押品。

// 偿还稳定币债务
function repay(uint256 amount) external nonReentrant {
    Vault storage vault = vaults[msg.sender];
    require(vault.debtAmount >= amount, "偿还过多");
    require(balanceOf(msg.sender) >= amount, "余额不足");

    _burn(msg.sender, amount);
    vault.debtAmount -= amount;

    if (vault.debtAmount == 0) {
        uint256 collateralToReturn = vault.collateralAmount;
        vault.collateralAmount = 0;
        (bool success, ) = msg.sender.call{value: collateralToReturn}("");
        require(success, "ETH 转账失败");
    }

    emit VaultUpdated(msg.sender, vault.collateralAmount, vault.debtAmount);
}

清算机制

清算函数对维护系统偿付能力至关重要。它允许任何人通过偿还用户的债务以市场价格获取其抵押品,来清算一个抵押不足的位置。

// 清算抵押不足的用户
function liquidate(address user) external nonReentrant {
    Vault storage vault = vaults[user];
    require(vault.debtAmount > 0, "没有债务可清算");
    require(getCurrentRatio(user) < LIQUIDATION_THRESHOLD, "位置不可清算");

    uint256 debtToRepay = vault.debtAmount;
    require(balanceOf(msg.sender) >= debtToRepay, "清算余额不足");

    uint256 ethPrice = getEthPrice();
    uint256 collateralToSeize = (debtToRepay * 1e8) / ethPrice;

    require(collateralToSeize <= vault.collateralAmount, "抵押品不足");

    vault.collateralAmount = 0;
    vault.debtAmount = 0;

    _burn(msg.sender, debtToRepay);

    (bool success, ) = msg.sender.call{value: collateralToSeize}("");
    require(success, "ETH 转账失败");

    emit Liquidated(user, msg.sender, debtToRepay, collateralToSeize);
    emit VaultUpdated(user, 0, 0);
}

当前比率

getCurrentRatio 函数计算用户保险库的当前抵押比率,这对于确定一个位置是否可以被清算至关重要。

// 获取抵押的当前比率
function getCurrentRatio(address user) public view returns (uint256) {
    Vault storage vault = vaults[user];
    if (vault.debtAmount == 0) return type(uint256).max;

    uint256 ethPrice = getEthPrice();
    uint256 collateralValue = (vault.collateralAmount * ethPrice) / 1e8;
    return (collateralValue * RATIO_PRECISION) / vault.debtAmount;
}

接受 ETH

最后,我们实现一个接收函数,使我们的合约能够接受 ETH 转账,这对于处理抵押品存入是必要的。

receive() external payable {}

这完成了我们的抵押稳定币实现。该智能合约通过超抵押和清算机制维持稳定,同时为用户提供铸造、偿还和管理其头寸的能力。再一次,这段代码并不是准备好用于生产,而是作为开始进行稳定币开发的简单示例。

接下来,让我们调用构建命令以编译合约:

forge build

之后,我们将编写测试(非常重要!)以确保所有功能和安全措施按预期工作。

编写测试

在你的 test 目录中创建一个名为 Stablecoin.t.sol 的文件,并按顺序输入以下代码片段:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/Stablecoin.sol";
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract MockV3Aggregator is AggregatorV3Interface {
    int256 private _price;
    uint8 private _decimals;

    constructor(uint8 decimals_, int256 initialPrice) {
        _decimals = decimals_;
        _price = initialPrice;
    }

    function setPrice(int256 price) external {
        _price = price;
    }

    function decimals() external view override returns (uint8) {
        return _decimals;
    }

    function description() external pure override returns (string memory) {
        return "Mock V3 Aggregator";
    }

    function version() external pure override returns (uint256) {
        return 1;
    }

    function getRoundData(uint80) external view override returns (uint80, int256, uint256, uint256, uint80) {
        return (0, _price, block.timestamp, block.timestamp, 0);
    }

    function latestRoundData() external view override returns (uint80, int256, uint256, uint256, uint80) {
        return (0, _price, block.timestamp, block.timestamp, 0);
    }
}

contract SimpleStablecoinTest is Test {
    SimpleStablecoin public stablecoin;
    MockV3Aggregator public mockPriceFeed;

    address public user1 = address(1);
    address public user2 = address(2);
    address public liquidator = address(3);

    uint8 public constant DECIMALS = 8;
    int256 public constant INITIAL_PRICE = 2000e8; // 每 ETH 2000 美元
    uint256 public constant INITIAL_ETH_BALANCE = 100 ether;

    event VaultUpdated(address indexed user, uint256 collateral, uint256 debt);
    event Liquidated(address indexed user, address indexed liquidator, uint256 debt, uint256 collateralSeized);

    function setUp() public {
        // 部署模拟价格预言机和稳定币
        mockPriceFeed = new MockV3Aggregator(DECIMALS, INITIAL_PRICE);
        stablecoin = new SimpleStablecoin(address(mockPriceFeed));

        // 设置测试账户
        vm.deal(user1, INITIAL_ETH_BALANCE);
        vm.deal(user2, INITIAL_ETH_BALANCE);
        vm.deal(liquidator, INITIAL_ETH_BALANCE);
    }

    function test_InitialState() public view {
        assertEq(stablecoin.name(), "Simple USD");
        assertEq(stablecoin.symbol(), "sUSD");
        assertEq(address(stablecoin.priceFeed()), address(mockPriceFeed));
    }

    function test_Mint() public {
        uint256 ethToMint = 1 ether;

        // 计算预期代币:
        // 1 ETH = $2000
        // 在 150% 的抵押比率下,最大债务为:
        // (2000 * 10000) / 15000 = 1333.33...
        uint256 collateralValue = (ethToMint * uint256(INITIAL_PRICE)) / 1e8;
        uint256 expectedTokens = (collateralValue * stablecoin.RATIO_PRECISION()) / stablecoin.COLLATERAL_RATIO();

        vm.startPrank(user1);

        stablecoin.mint{value: ethToMint}();

        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);
        assertEq(collateral, ethToMint);
        assertEq(debt, expectedTokens);
        assertEq(stablecoin.balanceOf(user1), expectedTokens);

        vm.stopPrank();
    }

    function test_MultipleVaultOperations() public {
        vm.startPrank(user1);

        // 初始铸造 1 ETH
        stablecoin.mint{value: 1 ether}();
        uint256 firstMintAmount = stablecoin.balanceOf(user1);

        // 添加更多抵押品(0.5 ETH)
        stablecoin.mint{value: 0.5 ether}();
        uint256 secondMintAmount = stablecoin.balanceOf(user1) - firstMintAmount;

        // 验证总头寸
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);
        assertEq(collateral, 1.5 ether);
        assertEq(debt, firstMintAmount + secondMintAmount);

        vm.stopPrank();
    }

    function test_Liquidation() public {
        // 1. 用户 1 创建一个 1 ETH 的保险库(神知道我们什么时候能到 10000...)
        vm.startPrank(user1);
        stablecoin.mint{value: 1 ether}();
        uint256 mintedAmount = stablecoin.balanceOf(user1);
        vm.stopPrank();

        // 2. 将代币转移给清算者
        vm.prank(user1);
        stablecoin.transfer(liquidator, mintedAmount);

        // 3. 将 ETH 价格降低到 1500 美元(低于清算阈值)
        mockPriceFeed.setPrice(1500e8);

        // 4. 检查头寸现在是否能被清算
        uint256 currentRatio = stablecoin.getCurrentRatio(user1);
        assertTrue(currentRatio < stablecoin.LIQUIDATION_THRESHOLD());

        // 5. 清算者执行清算
        vm.startPrank(liquidator);
        stablecoin.liquidate(user1);

        // 6. 验证清算结果
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);
        assertEq(debt, 0, "债务应为零");
        assertEq(collateral, 0, "抵押品应为零");
        vm.stopPrank();
    }

    function test_FullRepayment() public {
        // 设置:创建保险库并铸造代币
        vm.startPrank(user1);
        stablecoin.mint{value: 1 ether}();
        uint256 initialDebt = stablecoin.balanceOf(user1);

        // 偿还全部债务
        stablecoin.repay(initialDebt);

        // 验证全部还款和抵押品归还
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);
        assertEq(debt, 0);
        assertEq(collateral, 0);
        assertEq(stablecoin.balanceOf(user1), 0);
        vm.stopPrank();
    }

    function test_RevertWhen_MintingBelowMinCollateral() public {
        vm.startPrank(user1);
        vm.expectRevert("低于最低抵押");
        stablecoin.mint{value: 0.09 ether}();
        vm.stopPrank();
    }

    function test_LiquidationPrice() public {
        vm.startPrank(user1);

        // 创建一个 1 ETH 的头寸,价格为 2000 美元
        stablecoin.mint{value: 1 ether}();
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);

        // 计算清算价格
        uint256 liquidationPrice = (debt * stablecoin.LIQUIDATION_THRESHOLD() * 1e8) /
                                 (collateral * stablecoin.RATIO_PRECISION());

        // 将代币转移给清算者进行清算
        vm.stopPrank();
        vm.prank(user1);
        stablecoin.transfer(liquidator, debt);

        // 价格刚好高于清算 - 应该失败
        mockPriceFeed.setPrice(int256(liquidationPrice + 1e8));
        vm.prank(liquidator);
        vm.expectRevert("位置不可清算");
        stablecoin.liquidate(user1);

        // 价格低于清算 - 应该成功
        mockPriceFeed.setPrice(int256(liquidationPrice - 1e8));
        vm.prank(liquidator);
        stablecoin.liquidate(user1);
    }

    function test_PartialRepayment() public {
        // 设置:创建保险库并铸造代币
        vm.startPrank(user1);
        stablecoin.mint{value: 1 ether}();
        uint256 initialDebt = stablecoin.balanceOf(user1);

        // 偿还一半的债务
        uint256 repayAmount = initialDebt / 2;
        stablecoin.repay(repayAmount);

        // 验证部分还款
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user1);
        assertEq(debt, initialDebt - repayAmount);
        assertEq(collateral, 1 ether); // 抵押应保持不变
        vm.stopPrank();
    }
}

这个测试文件相当长,因此确保你回顾每个函数及其注释,以更好地理解其测试逻辑。

要运行测试,运行以下命令:

forge test

测试结果:

Ran 8 tests for test/Stablecoin.t.sol:SimpleStablecoinTest
[PASS] test_FullRepayment() (gas: 119625)
[PASS] test_InitialState() (gas: 25080)
[PASS] test_Liquidation() (gas: 158381)
[PASS] test_LiquidationPrice() (gas: 160892)
[PASS] test_Mint() (gas: 132465)
[PASS] test_MultipleVaultOperations() (gas: 148648)
[PASS] test_PartialRepayment() (gas: 136783)
[PASS] test_RevertWhen_MintingBelowMinCollateral() (gas: 23629)
Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 5.62ms (2.89ms CPU time)

现在我们的合约按预期工作,让我们将它们部署到像 Sepolia 这样的测试网。

部署稳定币

为了将其部署到像 Sepolia 这样的远程测试网,首先需要配置一些东西。

  1. 确保你的钱包中有足够的 ETH 以便部署和与你的稳定币合约进行交互
  2. 准备好你的钱包私钥和 QuickNode 端点 URL(稍后将在使用时用到)

接下来,在你的 script 文件夹中创建一个名为 DeployStablecoin.s.sol 的文件,并添加以下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import "../src/Stablecoin.sol";

contract DeployStablecoin is Script {
    function run() external {
        // Sepolia ETH/USD 价格预言机
        address priceFeedAddress = 0x694AA1769357215DE4FAC081bf1f309aDC325306;

        // 开始广播交易
        vm.broadcast();

        // 部署合约
        SimpleStablecoin stablecoin = new SimpleStablecoin(priceFeedAddress);

        console.log("稳定币部署到:", address(stablecoin));
        console.log("价格预言机地址:", priceFeedAddress);
    }
}

这个脚本使用 Chainlink 的价格预言机,并将合约部署到 Sepolia 测试网。

要部署合约,请运行以下命令:

forge script script/DeployStablecoin.s.sol:DeployStablecoin --rpc-url your_rpc_url --private-key your_private_key_here --broadcast -vvvv

-vvvv 选项用于更为详细的日志记录

最后,你将看到类似的输出:


##### sepolia
✅  [Success] Hash: 0xb9a5008e8acf16d72afa53f381c742bf1539cb610bf7dc9db6e23174c79d438c
Contract Address: 0x244FBFA8b2E02A0c5634d30Bb16E2d9B1B63Cb0d
Block: 7721469
Paid: 0.002110001463996228 ETH (2071014 gas * 1.018825302 gwei)

✅ Sequence #1 on sepolia | Total Paid: 0.002110001463996228 ETH (2071014 gas * avg 1.018825302 gwei)

==========================

链上执行完成且成功。

现在让我们移步到下一个步骤,通过存入 ETH 来铸造 sUSD。接着,我们将销毁 sUSD 以检索回 ETH。

管理稳定币操作

在你的脚本目录中创建一个名为 VaultOperations.s.sol 的文件,输入以下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../src/Stablecoin.sol";

contract VaultOperations is Script {
    SimpleStablecoin stablecoin;
    address constant STABLECOIN_ADDRESS = REPLACE_WITH_YOUR_CONTRACT_ADDRESS;  // 用你的合约地址替换

    function setUp() public {
        stablecoin = SimpleStablecoin(payable(STABLECOIN_ADDRESS));
    }

    function createAndMint() public {
        vm.broadcast();

        // 直接铸造抵押(保险库创建在铸造函数中处理)
        uint256 collateralAmount = 0.1 ether;
        stablecoin.mint{value: collateralAmount}();

        // 记录结果
        (uint256 collateral, uint256 debt) = stablecoin.vaults(msg.sender);
        console.log("创建保险库并存入", collateralAmount, "ETH");
        console.log("当前抵押品:", collateral);
        console.log("当前债务:", debt);
        console.log("当前比率:", stablecoin.getCurrentRatio(msg.sender));
    }

    function repayAndWithdraw(uint256 amount) public {
        (uint256 collateral, uint256 debt) = stablecoin.vaults(msg.sender);
        require(debt > 0, "没有债务可偿还");
        require(amount <= debt, "金额超出债务");

        vm.broadcast();
        stablecoin.repay(amount);

        // 记录结果
        (uint256 newCollateral, uint256 newDebt) = stablecoin.vaults(msg.sender);
        console.log("偿还", amount, "代币");
        console.log("归还的抵押品:", collateral - newCollateral);
        console.log("剩余债务:", newDebt);
    }

    function checkLiquidation(address user) public view {
        (uint256 collateral, uint256 debt) = stablecoin.vaults(user);
        if (debt == 0) {
            console.log("用户没有活动保险库");
            return;
        }

        uint256 currentRatio = stablecoin.getCurrentRatio(user);
        uint256 liquidationThreshold = stablecoin.LIQUIDATION_THRESHOLD();

        console.log("当前抵押品:", collateral);
        console.log("当前债务:", debt);
        console.log("当前比率:", currentRatio);
        console.log("清算阈值:", liquidationThreshold);
        console.log("可清算:", currentRatio < liquidationThreshold);
    }

    function liquidatePosition(address user) public {
        require(stablecoin.getCurrentRatio(user) < stablecoin.LIQUIDATION_THRESHOLD(), "位置不可清算");

        (uint256 collateral, uint256 debt) = stablecoin.vaults(user);
        console.log("尝试清算头寸,其抵押品为 %d 和债务 %d", collateral, debt);

        vm.broadcast();
        stablecoin.liquidate(user);

        console.log("头寸成功清算");
    }
}

此脚本演示了四个关键的保险库操作:

  • 创建一个保险库并铸造代币:存入 ETH 作为抵押并铸造 sUSD
  • 偿还债务:归还 sUSD 以检索你的 ETH 抵押品
  • 检查清算状态:监控保险库健康状况
  • 清算头寸:清算不安全的保险库

要使用这些函数,请在项目根目录下运行以下命令:

创建保险库并铸造代币

forge script script/VaultOperations.s.sol:VaultOperations --sig "createAndMint()" \
    --rpc-url https://your-quicknode-endpoint \
    --private-key your_private_key_here \
    --broadcast \
    -vvvv

这将创建一个带有 0.1 ETH 抵押的保险库(最低要求金额),并根据当前 ETH 价格(即铸造的代币数量将取决于 ETH 的价格)和抵押比率铸造最大安全金额的 sUSD。

示例交易

偿还债务并提取抵押品

还款金额需要用 wei 指定。以下示例显示了 1 sUSD 的部分还款(1e18 wei)。要获取全部抵押品,你需要偿还完整的债务金额。

forge script script/VaultOperations.s.sol:VaultOperations --sig "repayAndWithdraw(uint256)" 1000000000000000000 \
    --rpc-url https://your-quicknode-endpoint \
    --private-key your_private_key_here \
    --broadcast \
    -vvvv

这将还清未偿债务,并在如果债务完全偿还的情况下返回你的抵押品。

示例交易

检查保险库状态

forge script script/VaultOperations.s.sol:VaultOperations --sig "checkLiquidation(address)" "TARGET_ADDRESS" \
    --rpc-url https://your-quicknode-endpoint \
    -vvvv

示例输出:

脚本成功运行。

== 日志 ==
  当前抵押品: 100000000000000000
  当前债务: 178447799885333333333
  当前比率: 15084
  清算阈值: 13000
  可清算: false

这显示了保险库的当前健康状况,包括抵押比率和清算风险。

清算不安全的头寸

forge script script/VaultOperations.s.sol:VaultOperations --sig "liquidatePosition(address)" "TARGET_ADDRESS" \
    --rpc-url https://your-quicknode-endpoint \
    --private-key your_private_key_here \
    --broadcast \
    -vvvv

这将清算已降到清算阈值以下的保险库。

示例交易(请注意,此合约使用不同的参数以模拟清算)

信息

请记住:

  • 用你的已部署合约地址替换 YOUR_CONTRACT_ADDRESS
  • 用你的 RPC URL 替换 your-quicknode-endpoint
  • 使用你的实际私钥(绝不要分享或提交此内容!)
  • 对于清算,将 TARGET_ADDRESS 替换为你想要清算的保险库的地址

后续步骤- 增强的资产库机制:支持多种抵押品类型,根据市场情况实施动态比率,并创建更强大的抵押管理系统。研究 DAI 如何管理多重抵押职位,以及 Liquity 如何处理单一抵押稳定性。

  • 高级清算系统:建立更复杂的清算机制,如荷兰拍卖、部分清算和担保人激励结构。实施一个公平的系统,保持稳定,同时保护用户免受不必要的损失。
  • 价格预言机架构:开发一个更强大的价格馈送系统,使用多个预言机、TWAP(时间加权平均价格)机制和心跳检查。了解预言机攻击及如何防止它们。
  • 利率动态:创建动态稳定费,根据市场条件和利用率进行调整。研究像 Aave 这样的贷款协议如何实施利率曲线。
  • 编写生产就绪代码。你需要探索不同的漏洞,例如(预言机依赖、价格波动等)。建议查看现有的稳定币项目,审查它们的代码,看看你可以从中学习什么,并在自己的项目中实施。
  • 更多测试。然后进行审计。

如果你想看到此指南的第二部分,涵盖上述一些后续步骤,请在下面留下反馈!

最后的想法

就是这样!我们刚刚向你展示了如何使用 Foundry 创建自己的简单超额抵押稳定币进行智能合约开发和测试。

如果你有任何问题或需要帮助,请随时通过我们的 DiscordTwitter 联系我们。

我们 ❤️ 反馈!

让我们知道 如果你有任何反馈或新主题的请求。我们很想听到你的想法。

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。