Sepolia网络部署HeadOrTail合约遇UNPREDICTABLE_GAS_LIMIT错误求助
Sepolia测试网HeadOrTail合约resolveGame函数转账失败排查
问题概述
在Sepolia测试网部署Solidity编写的HeadOrTail猜正反合约,执行命令yarn hardhat run scripts/play_game.js --network sepolia时,调用resolveGame函数触发错误:execution reverted: Failed to transfer funds,错误码为UNPREDICTABLE_GAS_LIMIT。
合约代码(Game.sol)
pragma solidity ^0.8.0; contract HeadOrTail{ address public owner; event GameStarted(address indexed player1, address indexed player2, uint256 betAmount); event GameEnded(address indexed winner, address indexed loser, uint256 totalPrize); struct Game { address payable player1; address payable player2; uint256 betAmount; bool isActive; } mapping(uint256 => Game) public games; uint256 public gameIdCounter; constructor() { owner = msg.sender; } function startGame(address payable _player1, address payable _player2) external payable { require(_player1 != _player2, "Players cannot be the same"); require(msg.value % 2 == 0, "Total bet amount must be evenly divisible by 2"); uint256 gameId = gameIdCounter++; uint256 betAmount = msg.value; games[gameId] = Game(_player1, _player2, betAmount, true); emit GameStarted(_player1, _player2, betAmount); } function resolveGame(uint256 gameId) external { Game storage game = games[gameId]; require(game.isActive, "Game is not active"); uint256 randchoice = uint256( keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender)) ) % 2; address payable winner; address payable loser; if (randchoice == 0) { winner = game.player1; loser = game.player2; } else { winner = game.player2; loser = game.player1; } uint256 totalPrize = game.betAmount * 2; game.isActive = false; (bool sent, ) = winner.call{value: totalPrize}(""); require(sent, "Failed to transfer funds"); emit GameEnded(winner, loser, totalPrize); } }
脚本代码(play_game.js)
const hre = require("hardhat"); async function main() { // Hardcoded player addresses (Replace with actual addresses) // 0x1E7C116c770dCF79De5285d88b9d866Eac0Bf0B6 const player1 = "0x71780b13dEE6368508937Ce6C0FAB2cec63b376"; const player2 = "0xB91ebe10ec10EbBc566BF80E06a3c54D13383F4"; // Get signers (deployer will be used for deployment) const [deployer] = await hre.ethers.getSigners(); // Deploy the contract const HeadOrTail = await hre.ethers.getContractFactory("HeadOrTail"); const game = await HeadOrTail.deploy(); await game.deployed(); console.log(`Contract deployed to: ${game.address}`); // Each player sends 1 ETH as a bet const betAmount = hre.ethers.utils.parseEther("0.0001"); // Start the game (Use deployer or a specific signer) const tx = await game .connect(deployer) .startGame(player1, player2, { value: betAmount }); await tx.wait(); console.log(`Game started between ${player1} and ${player2}`); // Resolve the game const tx1 = await game.connect(deployer).resolveGame(0); await tx1.wait(); console.log(tx1); console.log(`Game resolved!`); } main().catch((error) => { console.error(error); process.exit(1); });
Hardhat配置(hardhat.config.js)
require("@nomiclabs/hardhat-waffle"); require("dotenv").config(); module.exports = { solidity: "0.8.0", networks: { hardhat: { chainId: 31337, }, localhost: { chainId: 31337, gasPrice: 875000000, }, sepolia: { url: process.env.SEPOLIA_RPC_URL, accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], saveDeployments: true, chainId: 11155111, }, localhost1: { url: "http://127.0.0.1:7545", chainId: 5777, gasPrice: 20000000000, }, }, namedAccounts: { deployer: { default: 0, 1: 0, }, player: { default: 1, }, }, };
完整错误信息
reason: 'execution reverted: Failed to transfer funds', code: 'UNPREDICTABLE_GAS_LIMIT', method: 'estimateGas', transaction: { from: '0x1E7C116c770dCF79De5285d88b9d866Eac0Bf0B6', to: '0xD50e52a1f81523f1B0625B3Ec66138ce38DC8d77', data: '0xb7a5a5b60000000000000000000000000000000000000000000000000000000000000000', accessList: null }, error: ProviderError: execution reverted: Failed to transfer funds
问题排查及解决方案
1. 核心问题:合约余额不足
合约中resolveGame函数尝试转出的totalPrize = game.betAmount * 2,但startGame函数仅接收了0.0001ETH,此时合约余额只有0.0001ETH,无法转出0.0002ETH,导致转账失败。
解决方法:
- 修正脚本中的投注金额:将
betAmount改为hre.ethers.utils.parseEther("0.0002"),确保合约收到足够的金额用于支付奖金; - 或调整合约逻辑:将
totalPrize改为game.betAmount,因为当前startGame的msg.value已经是总投注金额(两个玩家的合计),无需再乘以2。
2. 玩家地址格式错误
脚本中player1和player2的地址不完整(有效以太坊地址应为0x开头的42位字符串),这可能导致地址转换为payable时出现异常,间接影响转账。
解决方法:
- 替换为完整的有效测试网地址,确保地址长度符合标准。
3. 可选优化:转账方式与权限控制
- 使用
call时可添加gas限制避免异常,例如:winner.call{value: totalPrize, gas: 2300}(""); - 为
resolveGame函数添加权限控制,比如仅允许合约所有者或游戏参与者调用:
require(msg.sender == owner || msg.sender == game.player1 || msg.sender == game.player2, "Not authorized");
内容的提问来源于stack exchange,提问作者Aakash
相关产品推荐
相关产品推荐

