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

智能合约提币故障:Goerli ETH转入黑洞地址求助

合约提币异常:资金转入黑洞地址问题

我向合约转入Goerli ETH后,在Goerli Etherscan上能查到合约余额,但调用withdraw函数后,合约余额变为0,资金并未转入我的钱包地址,反而在内部交易中显示资金被转入了Goerli黑洞地址。提币交易有记录但无转账金额,求解决办法。

相关合约代码

基础合约 TimeControlled

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

contract TimeControlled {
    address public owner;
    address[] private ownerArr;
    uint256 dateForWithdrawal;
    address public contractAddress;

    mapping(address => bool) private addressToBool;

    constructor(bool isAllowed) public {
        owner == msg.sender;
        contractAddress == address(this);
        addressToBool[msg.sender] = isAllowed;
        addressToBool[address(this)] = isAllowed;
        ownerArr.push(owner);
        ownerArr.push(contractAddress);
    }

    // 向合约转入资金并返回true
    function fund() public payable returns (bool) {
        return true;
    }

    // 检查调用者是否为授权地址
    modifier owners() {
        require(addressToBool[msg.sender], "You are not allowed!");
        _;
    }

    // 提币函数
    function withdraw() public payable owners returns (bool) {
        require(address(this).balance > 0);
        payable(owner).transfer(address(this).balance);
        return true; /// @notice 从合约提取资金
        /// @dev 使用owners修饰符,返回布尔值
    }
}

继承合约 CallWithdraw

注:通过该合约的构造函数完成部署

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

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "./Timecontrolled.sol";

contract CallWithdraw is ChainlinkClient, TimeControlled {
    using Chainlink for Chainlink.Request;
    address public linkToken;
    uint256 public oraclePayment;
    address public oracle;
    bytes32 public jobId;
    uint256 public fundingPeriod = 5;

    constructor(
        address _linkToken,
        uint256 _oraclepayment,
        address _oracle,
        bytes32 _jobId
    ) public TimeControlled(true) {
        owner == msg.sender;
        oraclePayment = _oraclepayment;
        linkToken = _linkToken;
        oracle = _oracle;
        jobId = _jobId;
    }

    function callWithdraw() external {
        // 如果所有者未在设定时间调用提币函数,合约自动调用withdraw
        require(address(this).balance > 0);
        Chainlink.Request memory req = buildChainlinkRequest(
            jobId,
            address(this),
            this.fulfill.selector
        );
        req.addUint("until", block.timestamp + fundingPeriod * 1 minutes);
        withdraw();
        sendChainlinkRequestTo(oracle, req, oraclePayment);
    }

    function fulfill(bytes32 _requestId)
        public
        recordChainlinkFulfillment(_requestId)
    {
        address(this).balance == 0;
    }
}

部署脚本

from brownie import CallWithdraw, config
from scripts.helpful_scripts import get_account, network, get_contract
from web3 import Web3


oracle_payment = config["networks"][network.show_active()]["payment"]
oracle = config["networks"][network.show_active()]["oracle"]
job_Id = config["networks"][network.show_active()]["jobId"]


def deploy_call_withdraw_and_time_controlled():
    print(f"The network is {network.show_active()}")
    account = get_account()
    link_token = get_contract("link_token")
    print("Deploying contract...")
    call_withdraw = CallWithdraw.deploy(
        link_token.address,
        oracle_payment,
        oracle,
        job_Id,
        {"from": account},
    )
    """
    print("Transferring linktoken...")
        link_token.transfer(
        call_withdraw.address, Web3.toWei(1, "ether"), {"from": account}
    )
    print("Linktoken transferred successfully!")
    """

    print(account.balance())
    return call_withdraw


def fund():
    call_withdraw = CallWithdraw[-1]
    account = get_account()
    call_withdraw.fund({"value": Web3.toWei(0.03, "ether"), "from": account})
    print(f"{account.balance()} is the current balance")
    call_withdraw.withdraw({"from": account})
    print(f"{account.balance()} is the current balance")


def get_contract_balance():
    call_withdraw = CallWithdraw[-1]
    print(f"{call_withdraw.balance()} is {call_withdraw.address} balance")


def main():
    deploy_call_withdraw_and_time_controlled()
    fund()
    get_contract_balance()

问题根源与修复方案

核心错误:赋值运算符误用

合约中多处将赋值运算符=写成了相等判断符==,导致关键变量未被正确初始化:

  1. TimeControlled构造函数中,owner == msg.sender只是判断所有者是否等于调用者,并没有给owner变量赋值,owner保留默认值address(0)(即黑洞地址);
  2. CallWithdraw构造函数中同样存在owner == msg.sender的错误,没有覆盖父类的owner变量;
  3. 当withdraw函数执行payable(owner).transfer(address(this).balance)时,实际是把资金转到了address(0),也就是你看到的黑洞地址。

修复步骤

  1. 将所有错误的==替换为=:

    • 修正TimeControlled构造函数:
      constructor(bool isAllowed) public {
          owner = msg.sender;
          contractAddress = address(this);
          addressToBool[msg.sender] = isAllowed;
          addressToBool[address(this)] = isAllowed;
          ownerArr.push(owner);
          ownerArr.push(contractAddress);
      }
      
    • 修正CallWithdraw构造函数:
      constructor(
          address _linkToken,
          uint256 _oraclepayment,
          address _oracle,
          bytes32 _jobId
      ) public TimeControlled(true) {
          owner = msg.sender;
          oraclePayment = _oraclepayment;
          linkToken = _linkToken;
          oracle = _oracle;
          jobId = _jobId;
      }
      
  2. 额外优化:

    • withdraw函数不需要payable修饰,因为该函数是转出资金,无需接收ETH;
    • 删除fulfill函数中无效的address(this).balance == 0;代码,该判断无实际作用。

重要提示

已经转入合约的资金无法找回,因为address(0)是黑洞地址,没有任何私钥可以控制该地址的资产。后续部署修复后的合约时,务必仔细检查变量赋值逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 05:55:25