Hardhat V3 实战:期货合约从开发到部署的完整实现

10 阅读9分钟

前言

本文围绕金融衍生品中的期货展开,一方面系统梳理其核心理论知识,包括期货的定义、应用场景、针对行业痛点的解决价值、优劣势分析,以及从多维度对期货与现货进行对比研究;另一方面基于 Hardhat V3 开发框架,结合 OpenZeppelin V5 与 Solidity 0.8.24 技术栈,完整实现了期货相关合约从开发、测试到部署的全流程落地。

知识梳理

概述

现货是一手交钱一手交货的即时交易,期货是约定未来时间、价格交割的远期合约交易。

一、什么是期货

期货是交易所统一制定的标准化远期交易合约,交易双方约定未来特定时间、以固定价格买卖规定数量/质量的标的物(实物商品/金融资产),交易标的为合约本身而非标的物。核心特征:杠杆保证金(5%-15%)、双向交易(多/空)、T+0、交易所集中交易。分类:商品期货(原油、黄金、农产品等)、金融期货(股指、国债、外汇等)。

二、期货能做什么

  1. 企业端:套期保值,锁定未来购销价格,稳定生产经营节奏;
  2. 投资端:低买高卖合约投机盈利,赚取价格波动价差;
  3. 配置端:作为独立品种搭配股票、债券等,分散投资组合整体风险。

三、期货解决了什么

(一)企业端(核心)

  1. 解决现货价格波动风险,规避原材料采购、产品销售的价格不确定性,避免利润被吞噬;
  2. 解决未来定价难问题,依托期货权威价格提前锁定成本/利润,实现计划性经营。

(二)市场/投资端

  1. 解决现货定价缺乏前瞻性问题,集中竞价形成的期货价格成为现货定价基准;
  2. 解决投资渠道单一、仅单边盈利痛点,双向交易让涨跌均有盈利/避险机会;
  3. 解决市场风险无法转移问题,实现风险在套期保值者与投机者间合理分配,提升市场抗风险能力。

四、期货的核心优劣势

(一)核心优势

  1. 交易灵活:T+0日内无限次交易,双向交易,资金周转效率高,涨跌均有盈利空间;
  2. 杠杆增效:小资金撬动大额合约,提升资金利用率,实现以小博大;
  3. 价格公正:交易所集中竞价,信息公开透明,价格具权威性,无暗箱操作;
  4. 风险对冲:唯一能有效规避现货价格波动的工具,为企业经营保驾护航;
  5. 流动性足:市场参与者众多,合约成交活跃,开平仓无明显滑点。

(二)核心劣势

  1. 风险放大:杠杆双面性,亏损按合约全额计算,极端行情下易亏光保证金、被强平;
  2. 专业门槛高:对投资者分析能力要求高,新手因不熟悉规则易亏损;
  3. 强平风险:每日无负债结算,保证金不足未追加会被强制平仓,造成被动亏损;
  4. 交割限制:个人投资者不能参与实物交割,临近交割月需及时换月;
  5. 波动剧烈:受宏观、国际消息、资金炒作影响,短期波动远超现货,易引发非理性交易。

五、现货与期货多维度对比表

对比维度期货现货
交易标的交易所标准化合约标的物本身(实物/金融资产)
交易目的套期保值、投机、资产配置获取标的物,满足生产/消费/贸易需求
交易规则T+0、双向交易、杠杆保证金(5%-15%)多为T+1、单向交易为主、全款交易
结算方式每日无负债结算(逐日盯市)成交后即时/短期一次性结算
交收方式95%以上对冲平仓,极少交割必须完成标的物交收(一手交钱一手交货)
交易场所正规期货交易所(集中交易)场外市场(批发市场/电商)/部分现货交易所
价格属性前瞻性,反映未来供需预期即时性,反映当前供需关系
风险程度高(杠杆+强平+价格剧烈波动)低(仅承担标的物本身价格波动风险)
合约属性交易所统一制定,条款固定交易双方协商,非标准化
参与主体企业(套期保值)、个人/机构投资者(投机)生产/贸易/消费企业、普通采购者/投资者
资金门槛较低(保证金制度,小资金可参与)较高(全款交易,需足额资金)
监管主体国家金融监管部门(如证监会)市场监管部门,部分场外无明确监管

智能合约开发、测试、部署

智能合约

  • 代币合约
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.5.0
pragma solidity ^0.8.24;

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 BoykaYuriToken is ERC20, ERC20Burnable, Ownable, ERC20Permit {
    constructor(address recipient, address initialOwner)
        ERC20("MyToken", "MTK")
        Ownable(initialOwner)
        ERC20Permit("MyToken")
    {
        _mint(recipient, 1000000 * 10 ** decimals());
    }
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}
  • 期货合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title SimpleFutures
 * @dev 基于 USDC 保证金的简易期货合约
 */
contract SimpleFutures is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable marginToken;
    uint256 public constant LEVERAGE = 10; // 固定10倍杠杆
    uint256 public constant MAINTENANCE_MARGIN_RATE = 5; // 5% 维持保证金率
    uint256 public constant PRECISION = 1e18;
    struct Position {
        uint256 margin;      // 投入的保证金
        uint256 entryPrice;  // 开仓价格
        uint256 size;        // 合约头寸数量 (margin * leverage / entryPrice)
        bool isLong;         // 方向
        bool isOpen;         // 状态
    }

    mapping(address => Position) public positions;

    event PositionOpened(address indexed user, bool isLong, uint256 margin, uint256 entryPrice);
    event PositionClosed(address indexed user, int256 pnl, uint256 payout);

    constructor(address _marginToken) Ownable(msg.sender) {
        marginToken = IERC20(_marginToken);
    }

    /**
     * @notice 开启头寸
     * @param _margin 保证金金额
     * @param _entryPrice 当前预言机价格 (模拟输入)
     * @param _isLong 是否看涨
     */
    function openPosition(uint256 _margin, uint256 _entryPrice, bool _isLong) external nonReentrant {
        require(!positions[msg.sender].isOpen, "Position already exists");
        require(_margin > 0, "Margin must be > 0");

        marginToken.safeTransferFrom(msg.sender, address(this), _margin);

        uint256 size = (_margin * LEVERAGE * PRECISION) / _entryPrice;
        
        positions[msg.sender] = Position({
            margin: _margin,
            entryPrice: _entryPrice,
            size: size,
            isLong: _isLong,
            isOpen: true
        });

        emit PositionOpened(msg.sender, _isLong, _margin, _entryPrice);
    }

    /**
     * @notice 到期结算 (简单实现:由用户或后端调用,传入结算价格)
     */
    function closePosition(uint256 _settlePrice) external nonReentrant {
        Position storage pos = positions[msg.sender];
        require(pos.isOpen, "No active position");

        int256 pnl;
        if (pos.isLong) {
        pnl = (int256(pos.size) * int256(_settlePrice) / int256(PRECISION)) - 
              (int256(pos.size) * int256(pos.entryPrice) / int256(PRECISION));
            } else {
                pnl = (int256(pos.size) * int256(pos.entryPrice) / int256(PRECISION)) - 
                    (int256(pos.size) * int256(_settlePrice) / int256(PRECISION));
            }

        uint256 payout;
        int256 totalValue = int256(pos.margin) + pnl;

        if (totalValue <= 0) {
            payout = 0; // 爆仓
        } else {
            payout = uint256(totalValue);
        }

        pos.isOpen = false;
        if (payout > 0) {
            marginToken.safeTransfer(msg.sender, payout);
        }

        emit PositionClosed(msg.sender, pnl, payout);
    }
}

测试脚本

测试用例

  1. 初始化参数验证
  2. 能够成功开多仓
  3. 平仓结算逻辑:价格上涨多头应盈利
  4. 风险测试:跌破维持保证金应导致爆仓(Payout为0)
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import hre from "hardhat";
import { parseUnits, getAddress } from "viem";

describe("期货合约集成测试", async function () {
    // 获取连接
    const { viem } = await hre.network.connect();
    
    let owner: any, user: any;
    let publicClient: any;
    let futuresContract: any;
    let tokenContract: any;

    const INITIAL_PRICE = parseUnits("2000", 6); // 模拟初始价格 2000
    const MARGIN = parseUnits("100", 6);        // 100 USDC 保证金

    beforeEach(async function () {
        publicClient = await viem.getPublicClient();
        [owner, user] = await viem.getWalletClients();

        // 1. 部署代币 (假设 BoykaYuriToken 是标准的 ERC20)
        tokenContract = await viem.deployContract("BoykaYuriToken", [
            owner.account.address,
            owner.account.address
        ]);

        // 2. 部署期货合约
        futuresContract = await viem.deployContract("SimpleFutures", [
            tokenContract.address
        ]);
        // ⭐ 关键修复:给期货合约注入初始资金,用于支付用户的盈利
        const initialLiquidity = parseUnits("10000", 6); 
        await tokenContract.write.transfer([
            futuresContract.address, 
            initialLiquidity
        ], { account: owner.account });
        // 3. 给测试用户准备代币并授权
        // 假设合约中有 mint 函数,或者从 owner 转账
        await tokenContract.write.transfer([user.account.address, parseUnits("1000", 6)]);
        
        // 用户授权给期货合约
        await tokenContract.write.approve([futuresContract.address, MARGIN], {
            account: user.account
        });
    });

    it("初始化参数验证", async function () {
        const marginToken = await futuresContract.read.marginToken();
        assert.equal(
            getAddress(marginToken), 
            getAddress(tokenContract.address), 
            "保证金代币地址不匹配"
        );
        
        const leverage = await futuresContract.read.LEVERAGE();
        assert.equal(leverage, 10n, "杠杆倍数应为10");
    });

    it("应该能够成功开多仓", async function () {
        // 调用 openPosition
        const isLong = true;
        await futuresContract.write.openPosition([MARGIN, INITIAL_PRICE, isLong], {
            account: user.account
        });

        const pos = await futuresContract.read.positions([user.account.address]);
        
        assert.equal(pos[4], true, "仓位应当是开启状态"); // pos.isOpen
        assert.equal(pos[0], MARGIN, "保证金金额不符");
        assert.equal(pos[3], isLong, "方向应当是看涨(Long)");
    });

    it("平仓结算逻辑:价格上涨多头应盈利", async function () {
        // 1. 开仓
        await futuresContract.write.openPosition([MARGIN, INITIAL_PRICE, true], {
            account: user.account
        });

        // 2. 模拟价格上涨到 2200 (涨幅 10%)
        // 10倍杠杆下,100 保证金应赚取 100 * (2200-2000)/2000 * 10 = 100
        // 最终取回 100(本金) + 100(盈利) = 200
        const settlePrice = parseUnits("2200", 6);
        const balanceBefore = await tokenContract.read.balanceOf([user.account.address]);

        await futuresContract.write.closePosition([settlePrice], {
            account: user.account
        });

        const balanceAfter = await tokenContract.read.balanceOf([user.account.address]);
        const pos = await futuresContract.read.positions([user.account.address]);

        assert.equal(pos[4], false, "仓位应当已关闭");
        assert.ok(balanceAfter > balanceBefore, "结算后的余额应增加");
        
        // 验证具体数值 (200)
        const expectedPayout = parseUnits("200", 6);
        assert.equal(balanceAfter - balanceBefore, expectedPayout, "盈利金额计算错误");
    });

    it("风险测试:跌破维持保证金应导致爆仓(Payout为0)", async function () {
        // 开多仓
        await futuresContract.write.openPosition([MARGIN, INITIAL_PRICE, true], {
            account: user.account
        });

        // 价格大跌到 1500 (跌幅 25%,10倍杠杆直接穿仓)
        const settlePrice = parseUnits("1500", 6);
        
        await futuresContract.write.closePosition([settlePrice], {
            account: user.account
        });

        const pos = await futuresContract.read.positions([user.account.address]);
        assert.equal(pos[4], false);
        // 具体的余额校验逻辑可根据业务对“穿仓”后的处理添加断言
    });
});

部署脚本

// scripts/deploy.js
import { network, artifacts } from "hardhat";
import { parseUnits } from "viem";
async function main() {
  // 连接网络
  const { viem } = await network.connect({ network: network.name });//指定网络进行链接
  
  // 获取客户端
  const [deployer, investor] = await viem.getWalletClients();
  const publicClient = await viem.getPublicClient();
 
  const deployerAddress = deployer.account.address;
   console.log("部署者的地址:", deployerAddress);
  // 加载合约代币
  const MyTokenArtifact = await artifacts.readArtifact("BoykaYuriToken");

  // 部署(构造函数参数:recipient, initialOwner)
  const MyTokenHash = await deployer.deployContract({
    abi: MyTokenArtifact.abi,//获取abi
    bytecode: MyTokenArtifact.bytecode,//硬编码
    args: [deployerAddress, investor.account.address],//
  });

  // 等待确认并打印地址
  const MyTokenReceipt = await publicClient.waitForTransactionReceipt({ hash: MyTokenHash });
  console.log("MyToken合约地址:", MyTokenReceipt.contractAddress);
  // 部署SimpleFutures合约
  const SimpleFuturesArtifact = await artifacts.readArtifact("SimpleFutures");
  // 1. 部署合约并获取交易哈希
  const SimpleFuturesHash = await deployer.deployContract({
    abi: SimpleFuturesArtifact.abi,
    bytecode: SimpleFuturesArtifact.bytecode,
    args: [MyTokenReceipt.contractAddress],
  });
  const SimpleFuturesReceipt = await publicClient.waitForTransactionReceipt({ 
     hash: SimpleFuturesHash 
   });
   console.log("SimpleFutures合约地址:", SimpleFuturesReceipt.contractAddress);
}

main().catch(console.error);

结语

至此,本文已完成对期货这一金融衍生品核心理论知识的系统梳理,同时基于 Hardhat V3、OpenZeppelin V5 与 Solidity 0.8.24 技术栈,完整落地了期货相关代码从开发、测试到部署的全流程实践。从理论认知到技术落地的全维度覆盖,为期货相关的技术研究与应用落地提供了清晰的参考路径。