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
相关产品推荐
相关产品推荐

