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

如何为Solidity众筹合约编写withdraw(提现)函数?

众筹合约Withdraw函数问题修复方案

核心问题分析

  1. 资金托管逻辑错误:当前donateToCampaign直接将捐款打给项目所有者,合约未实际托管资金,导致withdraw执行时无余额可转。
  2. 提现条件不完整:截止时间判断被注释,不符合众筹常规规则(需截止时间过后且达标才能提现)。
  3. 转账安全性不足:使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:45:28