如何用区块链解决 “信任危机”?去中心化保险开发全揭秘

2 阅读7分钟

前言

本文围绕去中心化保险展开,系统梳理其相关理论体系与代码实操流程;整体基于 Hardhat V3 开发框架,依托 OpenZeppelin V5 合约库及 Solidity 0.8.24 + 版本,完整覆盖从合约开发、本地测试、部署上线到落地应用的全流程。

去中心化保险知识梳理

概述

去中心化保险是区块链技术与传统保险行业结合的产物,旨在通过智能合约、通证经济和去中心化自治组织,解决传统保险中存在的信任缺失、效率低下和不透明等痛点。

1. 核心定义

去中心化保险是指不依赖中心化保险公司或中介机构,而是利用区块链技术(主要是智能合约)来自动执行保险协议、管理资金池、评估风险并进行理赔的一种新型保险模式。

  • 本质:将保险业务逻辑代码化。
  • 载体:智能合约。

2. 主要特征

与传统保险相比,去中心化保险具有以下显著特征:

  • 透明性

    • 所有的保单条款、资金流向、理赔逻辑都写在智能合约中,链上公开可查,不可篡改。用户可以随时验证资金池的健康状况。
  • 自动化

    • 依靠 ** 预言机(Oracle)** 获取外部数据(如是否发生航班延误、资产是否被盗),一旦触发理赔条件,智能合约自动执行赔付,无需人工干预。
  • 无需信任

    • 用户不需要信任保险公司的 “良心”,只需要信任代码逻辑和数学算法。
  • 去中介化

    • 去除了传统保险的层层代理和行政机构,减少了中间环节的抽成和运营成本。

3. 核心优势

  • 降低成本:由于去除了中间商和自动化处理,运营成本和佣金大幅降低,从而可能提供更低的保费。
  • 防止拒赔风险:传统保险常因条款模糊或人为因素导致拒赔;去中心化保险由代码强制执行,只要满足合约预设条件,赔付是必然发生的。
  • 准入门槛低:基于公链的全球属性,任何有互联网连接的人(甚至无银行账户人群)都可以购买保险或成为承保人(提供流动性)。
  • 资金效率高:部分保险协议允许资金提供者(承保人)在提供保险资金的同时,将闲置资金用于其他 DeFi 收益挖矿,提高了资本利用率。

4. 典型应用场景

去中心化保险目前主要分为两大方向:DeFi 原生保险现实世界保险

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

智能合约

  • 预言机合约:为便于本地测试,我们会使用模拟预言机;而在生产环境中,则应使用专用的预言机工具库。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

contract MockWeatherOracle is AccessControl {
    bytes32 public constant REPORTER_ROLE = keccak256("REPORTER_ROLE");

    // requestId => 结果 (例如:航班是否延延误)
    mapping(bytes32 => bool) private _results;

    constructor(address admin, address reporter) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(REPORTER_ROLE, reporter);
    }

    // 由预言机节点(Reporter)调用,写入链外数据
    function fulfillData(bytes32 requestId, bool result) external onlyRole(REPORTER_ROLE) {
        _results[requestId] = result;
    }

    // 供保险合约查询
    function checkCondition(bytes32 requestId) external view returns (bool) {
        return _results[requestId];
    }
}
  • 保险合约
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import hre from "hardhat";
import { parseEther, keccak256, toBytes, formatEther } from "viem";
// 使用 node:test 时,需要确保你的 hardhat 环境已经正确加载 viem 扩展

// 全局变量类型可以根据实际合约ABI定义,这里使用 any 简化示例
let owner: any, investor: any, publicClient: any, testClient: any;
let WeatherOracle: any, SimpleInsurance: any;

describe("SimpleInsurance Test", async function () {
  // Hardhat 的 viem 扩展可以帮助获取客户端
  const { viem } = await hre.network.connect();
  
  beforeEach(async function () {
    publicClient = await viem.getPublicClient();
    // owner 账户将作为 Admin 和资金注入者
    // investor 账户将作为 User 和 Reporter(为了测试方便,实际中应不同)
    [owner, investor] = await viem.getWalletClients(); 
    testClient = await viem.getTestClient(); 

    console.log("owner 地址:", owner.account.address);
    console.log("investor (user/reporter) 地址:", investor.account.address);
    
    // 1. 部署 WeatherOracle
    // owner是admin, investor是reporter角色
    WeatherOracle = await viem.deployContract("MockWeatherOracle", [owner.account.address, investor.account.address]);
    console.log("WeatherOracle 地址:", WeatherOracle.address);
    
    // 2. 部署 SimpleInsurance
    SimpleInsurance = await viem.deployContract("SimpleInsurance", [ owner.account.address, WeatherOracle.address]);
    console.log("SimpleInsurance 地址:", SimpleInsurance.address);

    // 3. 注入资金池
    await owner.sendTransaction({
      to: SimpleInsurance.address,
      value: parseEther("20"), // 注入 20 ETH 作为理赔资金
    });
    console.log("资金池已注入 20 ETH");
  });

  it("购买与理赔流程测试", async function () {
    const policyIndex = BigInt(0);
    const requestId = keccak256(toBytes("AIRCRAFT_CRASH_EVENT_XYZ"));
    const premiumAmount = parseEther("1");
    
    // --- 步骤 A: 投资者 (用户) 购买保险 ---
    console.log("\n--- A. 用户购买保险 ---");
    const userBalanceBeforePurchase = await publicClient.getBalance({ address: investor.account.address });

    await SimpleInsurance.write.purchasePolicy({
      value: premiumAmount,
      account: investor.account,
    });

    const userBalanceAfterPurchase = await publicClient.getBalance({ address: investor.account.address });
    
    // 验证余额减少了至少 1 ETH (扣除 gas)
    assert(userBalanceAfterPurchase < userBalanceBeforePurchase, "用户余额应减少");
    console.log("用户投保成功,保费:", formatEther(premiumAmount), "ETH");

    // --- 步骤 B: 投资者 (扮演 Reporter 角色) 提交预言机数据 ---
    console.log("\n--- B. 预言机节点提交数据 ---");
    // 使用 investor 账户,因为它在 beforeEach 中被授予了 REPORTER_ROLE
    await WeatherOracle.write.fulfillData([requestId, true], {
      account: investor.account, 
    });
    
    const conditionStatus = await WeatherOracle.read.checkCondition([requestId]);
    assert.strictEqual(conditionStatus, true, "预言机数据应为真");
    console.log("预言机数据提交成功,条件满足。");


    // --- 步骤 C: 投资者 (用户) 发起理赔 ---
    console.log("\n--- C. 用户发起理赔 ---");
    const balanceBeforeClaim = await publicClient.getBalance({ address: investor.account.address });

    await SimpleInsurance.write.requestClaim([policyIndex, requestId], {
      account: investor.account,
    });
    
    const balanceAfterClaim = await publicClient.getBalance({ address: investor.account.address });

    // 验证余额增加显著,理赔金额为 10 ETH
    assert(balanceAfterClaim > balanceBeforeClaim, "理赔后用户余额应增加");
    console.log("理赔成功!余额变化:", formatEther(balanceAfterClaim - balanceBeforeClaim));
    
    // 验证保单状态
    // userPolicies 函数返回一个 struct: (uint256 coverage, bool isActive, bool isClaimed)
    // isClaimed 字段索引为 2
    const policyStatus = await SimpleInsurance.read.userPolicies([investor.account.address, policyIndex]);
    assert.strictEqual(policyStatus[2], true, "保单应标记为已理赔");
    console.log("测试通过:购买与理赔流程跑通。");
  });
});


测试脚本

测试说明:主要针对购买与理赔流程的测试

import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import hre from "hardhat";
import { parseEther, keccak256, toBytes, formatEther } from "viem";
// 使用 node:test 时,需要确保你的 hardhat 环境已经正确加载 viem 扩展

// 全局变量类型可以根据实际合约ABI定义,这里使用 any 简化示例
let owner: any, investor: any, publicClient: any, testClient: any;
let WeatherOracle: any, SimpleInsurance: any;

describe("SimpleInsurance Test", async function () {
  // Hardhat 的 viem 扩展可以帮助获取客户端
  const { viem } = await hre.network.connect();
  
  beforeEach(async function () {
    publicClient = await viem.getPublicClient();
    // owner 账户将作为 Admin 和资金注入者
    // investor 账户将作为 User 和 Reporter(为了测试方便,实际中应不同)
    [owner, investor] = await viem.getWalletClients(); 
    testClient = await viem.getTestClient(); 

    console.log("owner 地址:", owner.account.address);
    console.log("investor (user/reporter) 地址:", investor.account.address);
    
    // 1. 部署 WeatherOracle
    // owner是admin, investor是reporter角色
    WeatherOracle = await viem.deployContract("MockWeatherOracle", [owner.account.address, investor.account.address]);
    console.log("WeatherOracle 地址:", WeatherOracle.address);
    
    // 2. 部署 SimpleInsurance
    SimpleInsurance = await viem.deployContract("SimpleInsurance", [ owner.account.address, WeatherOracle.address]);
    console.log("SimpleInsurance 地址:", SimpleInsurance.address);

    // 3. 注入资金池
    await owner.sendTransaction({
      to: SimpleInsurance.address,
      value: parseEther("20"), // 注入 20 ETH 作为理赔资金
    });
    console.log("资金池已注入 20 ETH");
  });

  it("购买与理赔流程测试", async function () {
    const policyIndex = BigInt(0);
    const requestId = keccak256(toBytes("AIRCRAFT_CRASH_EVENT_XYZ"));
    const premiumAmount = parseEther("1");
    
    // --- 步骤 A: 投资者 (用户) 购买保险 ---
    console.log("\n--- A. 用户购买保险 ---");
    const userBalanceBeforePurchase = await publicClient.getBalance({ address: investor.account.address });

    await SimpleInsurance.write.purchasePolicy({
      value: premiumAmount,
      account: investor.account,
    });

    const userBalanceAfterPurchase = await publicClient.getBalance({ address: investor.account.address });
    
    // 验证余额减少了至少 1 ETH (扣除 gas)
    assert(userBalanceAfterPurchase < userBalanceBeforePurchase, "用户余额应减少");
    console.log("用户投保成功,保费:", formatEther(premiumAmount), "ETH");

    // --- 步骤 B: 投资者 (扮演 Reporter 角色) 提交预言机数据 ---
    console.log("\n--- B. 预言机节点提交数据 ---");
    // 使用 investor 账户,因为它在 beforeEach 中被授予了 REPORTER_ROLE
    await WeatherOracle.write.fulfillData([requestId, true], {
      account: investor.account, 
    });
    
    const conditionStatus = await WeatherOracle.read.checkCondition([requestId]);
    assert.strictEqual(conditionStatus, true, "预言机数据应为真");
    console.log("预言机数据提交成功,条件满足。");


    // --- 步骤 C: 投资者 (用户) 发起理赔 ---
    console.log("\n--- C. 用户发起理赔 ---");
    const balanceBeforeClaim = await publicClient.getBalance({ address: investor.account.address });

    await SimpleInsurance.write.requestClaim([policyIndex, requestId], {
      account: investor.account,
    });
    
    const balanceAfterClaim = await publicClient.getBalance({ address: investor.account.address });

    // 验证余额增加显著,理赔金额为 10 ETH
    assert(balanceAfterClaim > balanceBeforeClaim, "理赔后用户余额应增加");
    console.log("理赔成功!余额变化:", formatEther(balanceAfterClaim - balanceBeforeClaim));
    
    // 验证保单状态
    // userPolicies 函数返回一个 struct: (uint256 coverage, bool isActive, bool isClaimed)
    // isClaimed 字段索引为 2
    const policyStatus = await SimpleInsurance.read.userPolicies([investor.account.address, policyIndex]);
    assert.strictEqual(policyStatus[2], true, "保单应标记为已理赔");
    console.log("测试通过:购买与理赔流程跑通。");
  });
});


部署脚本

// 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 MockWeatherOracleArtifact = await artifacts.readArtifact("MockWeatherOracle");

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

  // 等待确认并打印地址
  const MockWeatherOracleReceipt = await publicClient.waitForTransactionReceipt({ hash: MockWeatherOracleHash });
  console.log("MockWeatherOracle合约地址:", MockWeatherOracleReceipt.contractAddress);
  // 部署SimpleBond合约
  const SimpleInsuranceArtifact = await artifacts.readArtifact("SimpleInsurance");
  // 1. 部署合约并获取交易哈希
  const SimpleInsuranceHash = await deployer.deployContract({
    abi: SimpleInsuranceArtifact.abi,
    bytecode: SimpleInsuranceArtifact.bytecode,
    args: [deployerAddress, MockWeatherOracleReceipt.contractAddress],
  });
  const SimpleBondReceipt = await publicClient.waitForTransactionReceipt({ 
     hash: SimpleInsuranceHash 
   });
   console.log("SimpleInsurance合约地址:", SimpleBondReceipt.contractAddress);
}

main().catch(console.error);

结语

至此,关于去中心化保险的核心理论体系梳理与代码实操落地已全部完成。从架构设计到合约编写,再到最终的部署与测试,我们已完整走完了从概念到应用的全流程。希望这份指南能为你在 Web3 金融领域的探索提供坚实的技术支撑