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
相关产品推荐
相关产品推荐

