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

Hardhat测试Solidity合约账户间ETH转账回滚问题排查

问题解答

1. 代码是否为账户1直接向账户2转账ETH的正确实现

该实现完全不符合预期目标,是错误的,核心问题如下:

  • EVM的基础规则决定了,合约没有权限直接划转外部地址(即你说的账户1/msg.sender)私钥控制的ETH。Solidity中地址的transfer()方法,划转的是当前部署代码的合约地址自身持有的ETH,根本不会扣减账户1的余额。
  • 你特意说明场景不是账户1向合约转ETH,但只要是通过合约执行的转账逻辑,就不可能绕开合约余额做中转:要么账户1直接发起一笔到账户2的普通链上转账(完全不需要部署合约),要么账户1调用合约时把ETH转到合约地址,合约再转发给账户2,不存在“合约不碰钱、直接把用户地址里的钱打给第三方”的可能,这是链上的基础安全设计。
  • 你代码里require(balance > 0.011 ether)的校验没有实际作用:这行只是检查账户1的总余额够多,但用户的钱存在自己地址里,不主动签名转到合约的话,合约永远拿不到这部分钱,检查用户总余额毫无意义。

2. 代码触发报错的具体原因

报错核心原因是合约自身的ETH余额不足,无法完成0.001 ETH的转出操作:

  • 代码里payable(__ADDRESS2__).transfer(0.001 ether)是从合约地址划钱给地址2,只要部署合约后没给合约地址充过ETH,或者调用pay函数时没有在交易的value字段附带至少0.001 ETH(函数的payable修饰符只是允许调用时给合约转ETH,不会自动从用户地址扣钱),合约余额就是0,执行transfer()时会直接回滚,抛出你看到的执行失败错误。
  • 小概率触发场景:如果__ADDRESS2__填的是合约地址,且该合约没有实现可接收ETH的receive()或fallback()函数,转账也会回滚,但绝大多数情况都是合约自身余额为0导致的失败。

正确实现参考

如果要实现“用户调用合约,自身出0.001 ETH转给地址2”的逻辑,代码可以调整为:

function pay(address _from, uint256 _tokenId) external payable noReentrant {
    // 校验用户调用时附带的ETH金额符合要求,不需要检查用户地址总余额
    require(msg.value == 0.001 ether, "Must send 0.001 ETH with transaction");
    // 将用户转入合约的ETH转给目标地址2
    payable(__ADDRESS2__).transfer(msg.value);
}

receive() external payable {}
fallback() external payable {}

用Hardhat测试时,调用pay函数需要传入value参数附带对应金额的ETH,比如:

await contract.pay(fromAddress, tokenId, {
    value: ethers.parseEther("0.001")
})

才能执行成功。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 15:06:23