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的写法正确捕获错误:
- 修改结果查询逻辑:
$("#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); } });
- 修改票数查询逻辑:
$("#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("你不是合约所有者,无权查看票数"); } });
- 优化交互体验:页面加载时就校验当前连接地址是不是所有者,非所有者直接隐藏查询类按钮,从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(); }
- 修正投票函数的传参错误,
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
相关产品推荐
相关产品推荐

