部署Solidity投票合约触发VM异常:invalid opcode问题求助
部署Ballot合约时触发VM Exception: invalid opcode错误排查与解决
问题场景
使用Ganache v2.7.1本地节点部署Ballot投票合约时,触发以下错误:
Error: Returned error: VM Exception while processing transaction: invalid opcode
附相关代码:
server.js文件
let web3; if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { web3 = new Web3(new Web3.providers.HttpProvider('HTTP://127.0.0.1:7545')); } var bytecode = fs.readFileSync('./voting_sol_Ballot.bin').toString() var abi = JSON.parse(fs.readFileSync('./voting_sol_Ballot.abi').toString()) const listOfCandidates = ['George W. Bush', 'Barack Obama', 'Joe Biden'] const votingContract = new web3.eth.Contract(abi); (async function () { const accounts = await web3.eth.getAccounts(); let candidates = new Map(); for (let i = 0; i < listOfCandidates.length; i++) { candidates[web3.utils.asciiToHex(listOfCandidates[i])] = listOfCandidates[i] } votingContract.deploy({ data: bytecode, arguments: [listOfCandidates.map(name => web3.utils.asciiToHex(name)), [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5], accounts[6], accounts[7]], "1622371495", "1622371495"] }).send({ from: accounts[0], gas: '0xF4240', gasPrice: '0x4A817C800' }).then((newContractInstance) => { votingContract.options.address = newContractInstance.options.address console.log("Contract address: " + newContractInstance.options.address) }) })();
Solidity合约文件
// SPDX-License-Identifier: GPL-3.0 pragma experimental ABIEncoderV2; pragma solidity >=0.7.0 <0.9.0; contract Ballot { struct Voter { uint token; bool voted; // if true, that person already voted uint vote; // index of the voted proposal address voterAddress; } struct Proposal { bytes32 name; // short name (up to 32 bytes) uint voteCount; // number of accumulated votes } address private government; mapping(address => Voter) public voters; Proposal[] public proposals; uint private winningVoteCount = 0; uint private tmpWinnerCount = 0; uint public totalVote; uint public voterCount; uint private voteFinishTime; uint private statViewFinishTime; modifier alreadyVoted() { require(!voters[msg.sender].voted, "Already voted."); _; } modifier multipleWinner() { _; require(winningVoteCount != 0, "No vote submitted."); require(tmpWinnerCount < 2, "Two or more candidate has been voted equally"); } modifier onlyVoters() { require(msg.sender != government, "Government cannot check vote."); _; } modifier onlyValidVoters() { require(voters[msg.sender].voted == false && voters[msg.sender].token != 0, "Only valid voters can cast a vote!"); _; } constructor(bytes32[] memory proposalNames, address[] memory voterArr, uint voteFinishTime_, uint statViewFinishTime_) { government = msg.sender; for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } totalVote = 0; voterCount = voterArr.length; voteFinishTime = voteFinishTime_; statViewFinishTime = statViewFinishTime_; for (uint i = 0; i < voterArr.length; i++) { voters[voterArr[i]].token = 1; } } function vote(uint proposal) public alreadyVoted() onlyValidVoters() { Voter storage sender = voters[msg.sender]; require(sender.token != 0, "Has no right to vote"); require(block.timestamp < voteFinishTime, "Election has been finished! You cannot vote."); sender.voted = true; sender.vote = proposal; proposals[proposal].voteCount += sender.token; totalVote += sender.token; sender.token -= 1; } function winningProposal() private multipleWinner() returns (uint winningProposal_) { for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; tmpWinnerCount = 1; } if (proposals[p].voteCount == winningVoteCount) { tmpWinnerCount += 1; } } } function winnerName() public returns (bytes32 winnerName_) { require(block.timestamp > voteFinishTime, "Election has not been finished yet."); winnerName_ = proposals[winningProposal()].name; } function checkMyVote() public view onlyVoters() returns (bytes32 proposalName) { require(voters[msg.sender].voted, "You have not voted yet!"); uint vote_index = voters[msg.sender].vote; return proposals[vote_index].name; } function getElectionResult() external view returns (bytes32[] memory names, uint[] memory voteCounts) { require( block.timestamp > statViewFinishTime, "Election has not been finished! You cannot view the results."); bytes32[] memory namesArr = new bytes32[](proposals.length); uint[] memory voteCountsArr = new uint[](proposals.length); for (uint i = 0; i < proposals.length; i++) { Proposal storage proposal = proposals[i]; namesArr[i] = proposal.name; voteCountsArr[i] = proposal.voteCount; } return (namesArr, voteCountsArr); } }
错误原因分析
winningProposal函数缺少返回语句:该函数声明返回uint类型,但执行路径结束时未明确返回变量,Solidity强制要求所有声明返回值的函数必须确保最终返回对应类型值,否则触发invalid opcode异常。multipleWinner修饰器逻辑顺序错误:修饰器采用后置执行(_;在前),会先执行winningProposal函数体,再检查require(winningVoteCount != 0),但初始winningVoteCount为0,无投票时直接触发require失败导致回滚。- 构造函数参数类型不匹配:server.js中传入的时间参数为字符串,而Solidity构造函数接收
uint类型,虽web3可能自动转换,但显式转换为数字可避免潜在类型错误。
修复步骤
1. 修复Solidity合约核心问题
// 修改multipleWinner修饰器,调整检查顺序并优化判断逻辑 modifier multipleWinner() { require(proposals.length > 0, "No proposals available."); _; require(tmpWinnerCount < 2, "Two or more candidates have equal votes"); } // 修复winningProposal函数,添加返回语句并重置状态变量 function winningProposal() private multipleWinner() returns (uint winningProposal_) { winningVoteCount = 0; tmpWinnerCount = 0; for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; tmpWinnerCount = 1; } else if (proposals[p].voteCount == winningVoteCount) { tmpWinnerCount += 1; } } return winningProposal_; // 必须添加返回语句 }
2. 调整server.js参数类型
将字符串时间戳转为数字:
votingContract.deploy({ data: bytecode, arguments: [ listOfCandidates.map(name => web3.utils.asciiToHex(name)), [accounts[1], accounts[2], accounts[3], accounts[4], accounts[5], accounts[6], accounts[7]], 1622371495, // 移除引号,转为uint类型 1622371495 ] }).send({ from: accounts[0], gas: '0xF4240', gasPrice: '0x4A817C800' })
3. 验证编译器与Ganache兼容性
确保合约使用的Solidity版本(>=0.7.0 <0.9.0)与Ganache v2.7.1兼容,建议锁定版本为pragma solidity ^0.7.0以避免兼容性问题。
额外优化建议
- 在
winnerName函数中添加数组边界检查,避免winningProposal()返回的索引超出proposals范围。 - 每次调用
winningProposal时重置winningVoteCount和tmpWinnerCount,避免历史状态干扰结果计算。
内容的提问来源于stack exchange,提问作者Gehad Elhawary
相关产品推荐
相关产品推荐

