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

Thrive合约withdraw()中call函数返回false导致转账失败求助

调用Thrive合约withdraw()函数时ETH转账失败的问题及解决方法

问题描述

调用Thrive合约的withdraw()函数提取ETH时,call函数返回false,触发合约中"Transfer failed"的require回滚。已将gas limit设置为3000000,仍无法解决该错误。

合约代码

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

contract Thrive {

    error InvalidDeadline(string message);

    enum campaignStatus {
        OPEN, 
        CLOSED
    }

    struct Campaign {
        address payable owner;
        string title;
        string description;
        uint256 opening;
        uint256 deadline;
        uint256 amountCollected;
        string image;
        campaignStatus status;
        address[] donators;
        uint256[] donations;
    }

    event Action (uint256 id, string actionType, address executor, uint256 timestamp);

    mapping(uint256 => Campaign) public campaigns;

    uint256 public numberOfCampaigns = 0;

    function createCampaign(address _owner, string memory _title, string memory _description, uint256 _deadline, string memory _image) public returns (uint256) {
        Campaign storage campaign = campaigns[numberOfCampaigns];
    
        if(_deadline < block.timestamp) {
            campaign.status = campaignStatus.CLOSED;
            revert InvalidDeadline("The deadline should be a date in the future");
        }
        campaign.owner = payable(_owner);
        campaign.title = _title;
        campaign.description = _description;
        campaign.opening = block.timestamp;
        campaign.deadline = _deadline;
        campaign.amountCollected = 0;
        campaign.image = _image;
        campaign.status = campaignStatus.OPEN;

        numberOfCampaigns++;

        emit Action (numberOfCampaigns - 1, "Campaign created", _owner, block.timestamp);

        return numberOfCampaigns - 1;
    }

    function donateToCampaign(uint256 _id) public payable {
        Campaign storage campaign = campaigns[_id];
        require(campaign.status == campaignStatus.OPEN, "The campaign is closed");

        uint256 amount = msg.value;

        campaign.donators.push(msg.sender);
        campaign.donations.push(amount);

        (bool sent,) = payable(campaign.owner).call{value: amount}("");
        require(sent, "transaction failed");

        if(sent) {
            campaign.amountCollected = campaign.amountCollected + amount;
        }

        emit Action (_id, "Fund donated to campaign", msg.sender, block.timestamp);
    }

    function withdraw(uint256 _id) public {
        Campaign storage campaign = campaigns[_id];

        require(campaign.status == campaignStatus.OPEN, "The campaign is closed");
        require(msg.sender == campaign.owner, "Withdrawer should be the owner of the campaign");
        require(campaign.amountCollected > 0, "Not enough fund generated");

        uint256 amount = campaign.amountCollected;
        require(amount > 0, "Amount not collected yet");

        (bool success, ) = payable(campaign.owner).call{value: amount}("");
        require(success, "Transfer failed");
 
        campaign.amountCollected = 0;
        campaign.status = campaignStatus.CLOSED;
        
        emit Action (_id, "Fund withdrawed", msg.sender, block.timestamp);
    }

    function getDonators(uint256 _id) view public returns (address[] memory, uint256[] memory) {
        return (campaigns[_id].donators, campaigns[_id].donations);
    }

    function getCampaigns() public view returns (Campaign[] memory) {
        Campaign[] memory allCampaigns = new Campaign[](numberOfCampaigns);

        for(uint i = 0; i < numberOfCampaigns; i++) {
            Campaign storage item = campaigns[i];

            allCampaigns[i] = item;
        }

        return allCampaigns;
    }

    function deleteCampaign(uint256 _id) public returns (uint256) {
        Campaign storage campaign = campaigns[_id];

        require(campaign.status == campaignStatus.CLOSED, "The campaign is open");

        campaign.owner = payable(address(0));
        campaign.title = "";
        campaign.description = "";
        campaign.opening = 0;
        campaign.deadline = 0;
        campaign.amountCollected = 0;
        campaign.image = "";

        numberOfCampaigns--;

        emit Action (_id, "Campaign deleted", msg.sender, block.timestamp);

        return numberOfCampaigns;
    }

    function getOwner() public view returns (address) {
        return address(this);
    }

    function getCampaignOwner(uint256 _id) public view returns (address) {
        return campaigns[_id].owner;
    }

    function getAmountCollected(uint256 _id) public view returns (uint256) {
        return campaigns[_id].amountCollected;
    }

}

错误日志

status  0x0 Transaction mined but execution failed
transaction hash    0xe3e3e02272ee5393d3d7d48e94972a83fee50b37d0d3f58283b3ece5507cf838
block hash  0x2ac76a4b78da8572c51720eb198912d60d00b27b537d7145424ab679f51f8965
block number    198
from    0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
to  Thrive.withdraw(uint256) 0x45373635641f5C51bf1029FdF2A225674D61AD45
gas 3000000 gas
transaction cost    35923 gas 
execution cost  14731 gas 
input   0x2e1...00000
decoded input   {
    "uint256 _id": "0"
}
decoded output  {}
logs    []
transact to Thrive.withdraw errored: Error occurred: revert.

revert
    The transaction has been reverted to the initial state.
Reason provided by the contract: "Transfer failed".
You may want to cautiously increase the gas limit if the transaction went out of gas.

解决方法

核心问题定位

donateToCampaign函数中,收到捐赠后直接将ETH转账至campaign.owner地址,导致合约本身没有留存ETH。而withdraw函数尝试从合约中提取ETH给owner时,合约余额为0,因此call函数返回false,触发回滚。

代码修正

修改donateToCampaign函数,将捐赠的ETH留存到合约中,而非直接转给owner:

function donateToCampaign(uint256 _id) public payable {
    Campaign storage campaign = campaigns[_id];
    require(campaign.status == campaignStatus.OPEN, "The campaign is closed");

    uint256 amount = msg.value;

    campaign.donators.push(msg.sender);
    campaign.donations.push(amount);

    // 直接将捐赠金额累加至amountCollected,ETH留存合约
    campaign.amountCollected += amount;

    emit Action (_id, "Fund donated to campaign", msg.sender, block.timestamp);
}

修改后,合约会持有捐赠的ETH,withdraw函数执行时即可正常将ETH转出给owner。

其他排查点

  • 确认campaign.owner是可接收ETH的地址:若owner是合约地址,需确保该合约有receive()或fallback()函数来接收ETH;
  • 无需调整gas limit:错误日志显示执行成本仅14731 gas,远低于设置的3000000,gas不足并非问题原因。

内容的提问来源于stack exchange,提问作者abhi9ab

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:35:03