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

部署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);
}
}

错误原因分析

  1. winningProposal函数缺少返回语句:该函数声明返回uint类型,但执行路径结束时未明确返回变量,Solidity强制要求所有声明返回值的函数必须确保最终返回对应类型值,否则触发invalid opcode异常。
  2. multipleWinner修饰器逻辑顺序错误:修饰器采用后置执行(_;在前),会先执行winningProposal函数体,再检查require(winningVoteCount != 0),但初始winningVoteCount为0,无投票时直接触发require失败导致回滚。
  3. 构造函数参数类型不匹配: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 19:49:55