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

