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

Solidity地址间资金转移方法及众筹合约提款故障咨询

以太坊众筹合约提款功能故障排查与修复

背景与现有代码

我正在开发基于以太坊的众筹网站,支持创建众筹活动。已实现生成收款地址的逻辑,代码如下:

function createMyFunDity(string memory _addressName) public checkDuplicateName(_addressName)  {
    address caller  = msg.sender;   
    list_of_creators.push(caller);                                             
    bytes32 hashedAddressName = keccak256(abi.encode(_addressName));
    hashedAddressList.push(hashedAddressName);// 存入哈希值用于重复检测
    bytes32 hashedString = keccak256(abi.encode(_addressName, msg.sender, block.timestamp));
    address castedAddress = address(uint160(uint256(hashedString)));
    address payable funditeeAddress = payable(castedAddress);
    funditees.push(FunDitees(funditeeAddress, _addressName));
    listOfFunDityAddresses.push(funditeeAddress);
    nameToAddress[_addressName] = funditeeAddress;
    creatorToAddressCreated[caller] = funditeeAddress;
    addressCreatedToCreator[funditeeAddress] = caller;
}

遇到的问题

目前无法实现提款功能,尝试编写的提款函数无法正常工作,代码如下:

function withdrawFromAddress(address payable _address) public {
    uint256 balance = address(_address).balance;
    bool sent = payable(address(this)).send(balance);
    require(sent, "Failed to send ETH");
}

问题分析

  1. 收款地址逻辑致命缺陷:通过哈希生成的funditeeAddress是普通外部账户地址,但没有对应的私钥,转入该地址的ETH会直接锁死,无法通过任何方式转出(该地址的私钥根本不存在)。
  2. 提款函数逻辑错误:合约无法主动转移外部账户的ETH,外部账户的转账只能由其私钥持有者发起。原函数试图把外部账户_address的余额转到合约本身,这在Solidity中完全无法实现。
  3. 权限缺失:原提款函数没有校验调用者是否是对应众筹活动的创建者,任何人都能调用,存在严重安全风险。

修复方案

方案一:改用合约内部余额映射管理(推荐,成本更低)

放弃生成外部收款地址,在合约内用映射记录每个众筹活动的余额,资金直接转入合约地址,提款时从合约余额中划转。

修改后的核心代码:

// 新增:记录每个众筹活动的余额(按活动名称映射)
mapping(string => uint256) public fundBalances;
// 新增:记录活动名称到创建者的映射
mapping(string => address) public nameToCreator;

// 修改创建函数:无需生成外部地址
function createMyFunDity(string memory _addressName) public checkDuplicateName(_addressName)  {
    address caller = msg.sender;   
    list_of_creators.push(caller);                                             
    bytes32 hashedAddressName = keccak256(abi.encode(_addressName));
    hashedAddressList.push(hashedAddressName);
    // 仅记录活动基本信息
    funditees.push(FunDitees(caller, _addressName));
    nameToCreator[_addressName] = caller;
}

// 新增:用户给众筹活动打款的函数
function contribute(string memory _addressName) public payable {
    require(nameToCreator[_addressName] != address(0), "Fundity does not exist");
    fundBalances[_addressName] += msg.value;
}

// 修复后的提款函数
function withdrawFromFundity(string memory _addressName) public {
    address creator = nameToCreator[_addressName];
    require(msg.sender == creator, "Only creator can withdraw");
    uint256 balance = fundBalances[_addressName];
    require(balance > 0, "No balance to withdraw");
    
    fundBalances[_addressName] = 0; // 先清零余额,防止重入攻击
    (bool sent,) = creator.call{value: balance}("");
    require(sent, "Failed to send ETH");
}

方案二:生成可控的代理合约收款地址

如果需要独立的收款地址,可以用最小代理合约(如OpenZeppelin的Clones库)创建每个众筹活动的代理,代理合约仅允许创建者提款。

核心思路:

  1. 编写基础众筹收款合约,包含仅创建者可调用的提款逻辑。
  2. 在主合约中用Clones库克隆该基础合约,作为每个众筹活动的收款地址。
  3. 克隆时将创建者地址传入代理合约,确保提款权限正确。

关键注意事项

  • 优先使用call{value: amount}("")而非send或transfer,因为send和transfer仅提供2300 gas,不足以处理复杂逻辑,而call可传递足够gas(需通过先清零余额的方式预防重入攻击)。
  • 必须添加权限校验,确保只有众筹活动的创建者才能发起提款。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 20:35:04