You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Insurance合约refundToInsurer()函数交易回滚问题排查求助

合约逻辑错误分析

问题背景

InsuranceProvider合约部署正常,调用newContract()可成功创建InsuranceConsumer合约;payOutContract()能正常将InsuranceConsumer合约的ETH转账至客户钱包,但refundToInsurer()函数调用时交易失败回滚,需排查逻辑错误。

合约代码

SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; 

contract InsuranceProvider {
    address payable public insurer;
    AggregatorV3Interface internal priceFeed;

    modifier onlyOwner() {
        require(insurer == msg.sender, "Only Insurance provider can do this");
        _;
    }

    constructor() payable {
        priceFeed = AggregatorV3Interface(
            0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
        );
        insurer = payable(msg.sender);
    }

    function newContract(
        address payable _client,
        uint256 _premium,
        uint256 _payoutValue
    ) public payable onlyOwner returns (address) {
        //create contract, send payout amount so contract is fully funded plus a small buffer
        InsuranceConsumer i = (new InsuranceConsumer){
            value: ((_payoutValue * 1 ether) / (uint256(getLatestPrice())))
        }(_client, _premium, _payoutValue);

        return address(i);
    }

    function getLatestPrice() public view returns (int256) {
        (, int256 price, , uint256 timeStamp, ) = priceFeed.latestRoundData();
        // If the round is not complete yet, timestamp is 0
        require(timeStamp > 0, "Round not complete");
        return price;
    }

    function payOutContract(address _contract) public {
        InsuranceConsumer i = InsuranceConsumer(_contract);
        // Transfer agreed amount to client
        i.payOutContract();
    }

    function refundToInsurer(address _contract) public onlyOwner  {
        InsuranceConsumer i = InsuranceConsumer(_contract);
        // Transfer back the amount to insurer 
        i.refundToInsurer();
   }
}

contract InsuranceConsumer {
    AggregatorV3Interface internal priceFeed;
    address payable public insurer;
    address payable client;
    uint256 startDate;
    uint256 premium;
    uint256 payoutValue;

    constructor(
        address payable _client,
        uint256 _premium,
        uint256 _payoutValue
    ) payable {
        //set ETH/USD Price Feed
        priceFeed = AggregatorV3Interface(
            0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e
        );

        //first ensure insurer has fully funded the contract
        require(
            msg.value >= _payoutValue / uint256(getLatestPrice()),
            "Not enough funds sent to contract"
        );

        //now initialize values for the contract
        insurer = payable(msg.sender);
        client = _client;
        startDate = block.timestamp; //contract will be effective immediately on creation
        premium = _premium;
        payoutValue = _payoutValue;
    }

    function payOutContract() public {
        //Transfer agreed amount to client
        client.transfer(address(this).balance);
    }

    function refundToInsurer() public {
        // Transfer back the amount to insurer
        insurer.transfer(address(this).balance);
    }

    function getLatestPrice() public view returns (int256) {
        (, int256 price, , uint256 timeStamp, ) = priceFeed.latestRoundData();
        // If the round is not complete yet, timestamp is 0
        require(timeStamp > 0, "Round not complete");
        return price;
    }
}

逻辑错误说明

1. Insurer地址赋值错误(核心回滚原因)

在InsuranceConsumer的构造函数中,insurer = payable(msg.sender);存在致命错误:

  • 由于InsuranceConsumer是由InsuranceProvider合约创建的,此时构造函数的msg.sender是InsuranceProvider合约地址,而非实际的保险人钱包地址(即部署InsuranceProvider的用户地址)。
  • 这导致refundToInsurer()执行时,实际是尝试将ETH转账至InsuranceProvider合约而非预期的保险人钱包,若测试时预期转至个人钱包,或后续存在隐性权限校验,会直接引发交易回滚。

修复方案:
修改InsuranceProvider的newContract()函数,将合约内存储的insurer地址作为参数传递给InsuranceConsumer构造函数;同时调整InsuranceConsumer的构造函数接收该地址并赋值:

// 修改InsuranceProvider的newContract函数
function newContract(
    address payable _client,
    uint256 _premium,
    uint256 _payoutValue
) public payable onlyOwner returns (address) {
    InsuranceConsumer i = (new InsuranceConsumer){
        value: ((_payoutValue * 1 ether) / (uint256(getLatestPrice())))
    }(insurer, _client, _premium, _payoutValue); // 传递保险人地址
    return address(i);
}

// 修改InsuranceConsumer的构造函数
constructor(
    address payable _insurer, // 新增保险人地址参数
    address payable _client,
    uint256 _premium,
    uint256 _payoutValue
) payable {
    priceFeed = AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
    require(
        msg.value >= _payoutValue / uint256(getLatestPrice()),
        "Not enough funds sent to contract"
    );
    insurer = _insurer; // 使用传入的保险人钱包地址
    client = _client;
    startDate = block.timestamp;
    premium = _premium;
    payoutValue = _payoutValue;
}

2. 核心函数缺少权限控制(安全风险+潜在回滚诱因)

InsuranceConsumer的payOutContract()和refundToInsurer()均未添加权限校验:

  • 任何外部地址都可调用这两个函数转走合约资金,存在严重安全漏洞。
  • 若恶意用户提前调用payOutContract()转走全部余额,后续调用refundToInsurer()会因合约余额为0导致转账失败回滚。

修复方案:
为两个函数添加权限控制,仅允许保险人或InsuranceProvider合约调用:

// 在InsuranceConsumer合约中添加权限校验modifier
modifier onlyAuthorized() {
    require(msg.sender == insurer || msg.sender == address(InsuranceProvider(msg.sender)), "Unauthorized caller");
    _;
}

// 修改核心函数
function payOutContract() public onlyAuthorized {
    client.transfer(address(this).balance);
}

function refundToInsurer() public onlyAuthorized {
    insurer.transfer(address(this).balance);
}

内容的提问来源于stack exchange,提问作者SYED ASAD KAZMI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 09:45:43