如何为Solidity众筹合约编写withdraw(提现)函数?
众筹合约Withdraw函数问题修复方案
核心问题分析
- 资金托管逻辑错误:当前
donateToCampaign直接将捐款打给项目所有者,合约未实际托管资金,导致withdraw执行时无余额可转。 - 提现条件不完整:截止时间判断被注释,不符合众筹常规规则(需截止时间过后且达标才能提现)。
- 转账安全性不足:使用
transfer虽有gas限制,但未处理转账失败的极端情况。
修正后的完整合约
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract CrowdFunding { event DonationReceived(uint256 campaignId, address donator, uint256 amount); event FundsWithdrawn(uint256 campaignId, address owner, uint256 amount); struct Campaign { address payable owner; string title; string description; uint256 target; uint256 deadline; // 存储毫秒级时间戳 uint256 amountCollected; string image; address[] donators; uint256[] donations; } mapping(uint256 => Campaign) public campaigns; uint256 public numberOfCampaigns = 0; function createCampaign( address _owner, string memory _title, string memory _description, uint256 _target, uint256 _deadline, string memory _image ) public returns (uint256) { uint256 currentTimeInMs = block.timestamp * 1000; require(_deadline > currentTimeInMs, "The deadline must be in the future"); Campaign storage campaign = campaigns[numberOfCampaigns]; campaign.owner = payable(_owner); campaign.title = _title; campaign.description = _description; campaign.target = _target; campaign.deadline = _deadline; campaign.image = _image; campaign.amountCollected = 0; numberOfCampaigns++; return numberOfCampaigns - 1; } function donateToCampaign(uint256 _id) public payable { require(msg.value > 0, "Donation amount must be greater than 0"); Campaign storage campaign = campaigns[_id]; require(block.timestamp * 1000 < campaign.deadline, "Campaign has ended"); // 将捐款存入合约托管,而非直接打给所有者 campaign.donators.push(msg.sender); campaign.donations.push(msg.value); campaign.amountCollected += msg.value; emit DonationReceived(_id, msg.sender, msg.value); } function getDonators(uint256 _id) public view 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 (uint256 i = 0; i < numberOfCampaigns; i++) { allCampaigns[i] = campaigns[i]; } return allCampaigns; } function withdraw(uint256 _id) public { Campaign storage campaign = campaigns[_id]; uint256 currentTimeInMs = block.timestamp * 1000; // 校验提现条件:所有者调用、截止时间已过、达到目标金额 require(msg.sender == campaign.owner, "Only campaign owner can withdraw"); require(currentTimeInMs > campaign.deadline, "Campaign deadline not reached"); require(campaign.amountCollected >= campaign.target, "Campaign did not reach target"); require(campaign.amountCollected > 0, "No funds to withdraw"); // 使用call方式转账,处理转账失败情况 uint256 withdrawAmount = campaign.amountCollected; campaign.amountCollected = 0; // 先重置金额,防止重入攻击 (bool success, ) = campaign.owner.call{value: withdrawAmount}(""); require(success, "Withdrawal failed"); emit FundsWithdrawn(_id, campaign.owner, withdrawAmount); } }
关键修改说明
- 资金托管修正:
donateToCampaign不再直接转账给所有者,而是将资金存入合约,同时更新amountCollected。 - 完善提现条件:恢复并强化截止时间判断,确保只有截止时间过后且达标,所有者才能提现。
- 安全优化:
- 提现前先重置
amountCollected,防止重入攻击。 - 使用
call方式转账,并通过require确保转账成功,避免因接收者合约拒绝接收导致资金滞留。 - 添加事件
DonationReceived和FundsWithdrawn,方便链上监控。
- 提现前先重置
内容的提问来源于stack exchange,提问作者BLIND
相关产品推荐
相关产品推荐

