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

Solidity onlyOwner受限函数可被任意钱包访问的修复方案

问题原因
  • 核心认知偏差:公链上所有合约存储数据都是完全公开的。给view/pure类型函数加权限修饰符,本质上只能限制调用者走合约函数接口的模拟执行,根本无法阻止他人通过底层RPC接口直接读取合约存储槽的明文数据。如果业务要求投票结果必须在所有者公布前保密,不能把投票数据明文存在链上。
  • 前端调用逻辑错误:调用带权限校验的view函数时,没有给.call()方法显式传入from参数指定当前连接的钱包地址。本地测试节点(Ganache、Hardhat Network等)处理不带from参数的eth_call请求时,默认会用节点预置的第一个账户(也就是部署合约的所有者地址)作为msg.sender执行模拟,不管你在钱包里切换什么地址,执行时的身份都是所有者,自然不会触发修饰符的拦截逻辑。
  • 错误捕获逻辑不规范:用旧版回调写法处理call返回,没有正确捕获合约revert错误,就算调用被拦截也可能收不到报错,误以为非所有者也能调用成功。
  • 代码细节问题:修饰符命名拼写错误(把标准的onlyOwner拼成了onlyonwner),虽然当前代码内定义和引用的拼写一致不影响运行,但后续维护很容易出现拼写不匹配的bug。另外调用send()方法时传入的from参数是整个账户数组,虽然单元素数组转字符串可能碰巧生效,但写法不规范容易触发地址格式错误。
可行解决方案

如果只是要实现前端交互层面的拦截:非所有者调用权限函数时报错、只有所有者能正常拿到返回,按以下修改即可;如果要实现真正的投票期结果保密,需要改用承诺-揭示(Commit-Reveal)投票方案,投票阶段用户只提交加密后的投票哈希,投票期结束后再统一揭示结果,避免明文数据上链被直接读取。

前端代码修改

所有调用带权限修饰符的view函数时,必须显式传入当前选中的钱包地址作为from参数,同时用async/await+catch的写法正确捕获错误:

  1. 修改结果查询逻辑:
$("#res").click(async function() {
  try {
    const currentAccount = accounts[0];
    // 显式传入当前调用者地址
    const result = await contract.methods.declare_winner().call({from: currentAccount});
    alert(result);
  } catch (err) {
    alert("你不是合约所有者,无权查看结果");
    console.error(err);
  }
});
  1. 修改票数查询逻辑:
$("#show-vote1").click(async function(){
  try {
    const currentAccount = accounts[0];
    const count = await contract.methods.pati1_cnt_VOTE().call({from: currentAccount});
    alert("候选人1的得票数为:"+count);
  } catch (err) {
    alert("你不是合约所有者,无权查看票数");
  }
});

$("#show-vote2").click(async function(){
  try {
    const currentAccount = accounts[0];
    const count = await contract.methods.pati2_cnt_VOTE().call({from: currentAccount});
    alert("候选人2的得票数为:"+count);
  } catch (err) {
    alert("你不是合约所有者,无权查看票数");
  }
});
  1. 优化交互体验:页面加载时就校验当前连接地址是不是所有者,非所有者直接隐藏查询类按钮,从UI层提前拦截:
const ownerAddr = await contract.methods.owner().call();
const currentAddr = accounts[0].toLowerCase();
if(currentAddr !== ownerAddr.toLowerCase()){
  $("#res").hide();
  $("#show-vote1").hide();
  $("#show-vote2").hide();
}
  1. 修正投票函数的传参错误,send方法的from参数取账户数组的第一个元素即可,不要传入整个数组:
// 候选人1投票逻辑
contract.methods.participant1_vote().send({from: accounts1[0]}, function(err,res){
  // 原有回调逻辑
})
// 候选人2投票逻辑同理,from参数传accounts2[0]

合约端可选优化

  • 把修饰符名统一修正为标准的onlyOwner,避免后续维护出现拼写错误。
  • 现有修饰符的权限校验逻辑本身是生效的,只要前端调用时正确传入from参数,非所有者调用就会被revert,不需要修改权限判断逻辑。
  • 如果要实现更高等级的结果保密,不要把投票计数明文存在公开mapping里,改用加密投票方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:15:29