热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一种链下绕过非view限制直接读取智能合约某类特殊函数返回结果的技巧相关的知识,希望对你有一定的参考价值。






一、前言

我们知道,在智能合约中一般分为读取(view/pure)或者写入(改变状态)这两种类型。由于保护数据设置障碍的需要,有时合约开发者并不想别人查看他们的view函数返回值,于是在上面增加了调用者权限限制,更有甚者,故意通过某种技巧(或者是业务需要)将此函数变成非view/pure类型的函数,也就是一个交易。这样线下(前端或者脚本)调用此函数时就是一个交易,是无法直接得到函数返回结果的。

在上一篇文章《一种绕过管理员权限调用智能合约view函数的小技巧》中,我们介绍了绕过权限控制来读取view函数返回结果的技巧,那么问题来了,这里的函数是非ivew的怎么办?如果该函数的实际执行并未改变状态,相当于一个view函数执行,那我们是可以绕过这个限制的。本文就介绍这么一个小技巧,同时也是对《使用ethers.js直接读取智能合约中插槽内容》这一篇文章的收尾问题进行解答,验证我们读取的数据和实际数据是相同的。


二、示例目标合约。

我们还是以《使用ethers.js直接读取智能合约中插槽内容》这一篇文章中的示例合约进行演示,再次贴出该合约地址:https://bscscan.com/address/0x4BfE9489937d6C0d7cD6911F1102c25c7CBc1B5A#code
该合约已经通过浏览器验证,是BSC网络上一个真实运行的合约(当然不是笔者部署的)。我们注意看以下代码:

bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Modifier to check whether the `msg.sender` is the admin.
* If it is, it will run the function. Otherwise, it will delegate the call
* to the implementation.
*/

modifier ifAdmin() {
if (msg.sender == _admin()) {
_;
} else {
_fallback();
}
}
/**
* @return The address of the proxy admin.
*/

function admin() external ifAdmin returns (address) {
return _admin();
}
/**
* @return The address of the implementation.
*/

function implementation() external ifAdmin returns (address) {
return _implementation();
}

有看过我们面文章的人一看就知道我摘出来的代码的含义,没有看过的也不打紧,我简单再解释一下。这个代码块刚开始定义了admin和implement的插槽索引,然后定义了一个ifAdmin的函数修饰符,在其定义里我们看到,如果调用者是管理员,就执行接下来的函数体,如果不是,就执行_fallback()函数,这里的_fallback()其实调用委托地址相同的函数了,弄明白这个需要懂得代理/委托这种可升级模式。读者也可以在网上搜索或者在我的其它文章中读到代理/委托相关内容,这里不再解释。

从上面的代码中我们可以看到,只有管理员才能查询管理员地址和implementation地址,并且它们都是非view函数的,无法直接在浏览器界面上使用Read Contract 调用,而是出现在 Write Contract界面中,如下图:
Read Contract按钮下空空如也
上面那张图显示了Read Contract按钮下空空如也。我们再看Write Contract。
读取信息出现在写合约界面中
从上图可以看到,本来只是读取管理员信息的操作却显示在了write界面中,显然第一我们不是管理员,第二我们也没有管理员私钥,我们执行写操作是不能成功的。再说,即使成功,我们也只能得到一个交易对象,而无法直接得到返回结果。

那个Connedt to Web3 点击后会连接你的浏览器插件钱包(笔者用的metamask),当网络选择正确时,连接成功后会变绿,显示连接成功和你的地址,这里笔者就不再放图了,这时你就可以直接使用浏览器调用合约了,不需要该死的项目方前端界面了😂😂😂。

从合约的代码片断中我们可以看到,只有管理员才能去读谁是管理员,这个和区块链的公开透明有那么一点点相违背啊。这里也许并不是合约开发者存心和我们为难,不让我们读这个数据。很大可能是直接使用了openzeppelin中的代理/委托标准模板,那就是openzeppelin和我们为难了,但不管怎样,这都不是事。

好了,有些扯远了,我们来看怎么通过脚本去调用这个write类型函数并得到结果。


三、测试脚本

首先,我们需要明确一点,我们调用的admin函数和implementation函数在调用者是管理员的情况下,的确只读取了本地存储的adminimplement地址,并无改变任何状态,其实质相当于是一个view函数。(如果我们不是管理员调用会报错,即使是view函数也会报错)。

要解决这个问题首先得是管理员,当然我们不是管理员,也拿不到私钥,怎么办呢?上一篇文章《一种绕过管理员权限调用智能合约view函数的小技巧》已经解决这个问题了。

其次,这个不是view函数怎么办?如果我们在该合约的浏览器页面下的Contract ABI里找到这个admin函数,相应的ABI内容为:
{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"}

我们注意到stateMutability标记的为nonpayable,而不是view

因此,我们无法直接使用它公开的ABI了,我们需要自己编一个view类型的自用。

const abi = [
"function admin() view returns(address)",
"function implementation() view returns(address)"
]

我们今天的脚本在前两篇文章的脚本上稍微改一下就能得到了,完整脚本如下:

const { ethers,utils } = require("ethers");
const bsc_rpc_url = "https://bsc-dataseed2.defibit.io"
const provider = new ethers.providers.JsonRpcProvider(bsc_rpc_url)
const proxy_address = "0x4BfE9489937d6C0d7cD6911F1102c25c7CBc1B5A"
const admin_slot = ethers.BigNumber.from("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")
const impl_slot = ethers.BigNumber.from("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")
const abi = [
"function admin() view returns(address)",
"function implementation() view returns(address)"
]
const proxy_contract = new ethers.Contract(proxy_address,abi,provider)
async function checkResult(admin_address,impl_address) {
try {
let admin = await proxy_contract.admin({
"from":admin_address
})
console.log("check admin_address:", admin === admin_address)
let impl = await proxy_contract.implementation({
"from":admin_address
})
console.log("check impl_address:", impl === impl_address)
}catch(e){
console.log(e)
}
}
async function start() {
const admin_info = await provider.getStorageAt(proxy_address , admin_slot)
console.log("admin_info:",admin_info)
const admin_address = utils.getAddress("0x" + admin_info.substring(26))
console.log("admin_address:",admin_address)
console.log()
const impl_info = await provider.getStorageAt(proxy_address , impl_slot)
console.log("impl_info:",impl_info)
const impl_address = utils.getAddress("0x" + impl_info.substring(26))
console.log("impl_address:",impl_address)
console.log()
checkResult(admin_address,impl_address)
}
start()

我们直接node运行脚本,得到的输出为:

admin_info: 0x0000000000000000000000005379f32c8d5f663eacb61eef63f722950294f452
admin_address: 0x5379F32C8D5F663EACb61eeF63F722950294f452
impl_info: 0x000000000000000000000000cac73a0f24968e201c2cc326edbc92a87666b430
impl_address: 0xcac73A0f24968e201c2cc326edbC92A87666b430
check admin_address: true
check impl_address: true

注意:我们这里的流程是先利用插槽索引得到管理员地址,再利用管理员地址去调用这个admin函数,这样双重验证了管理员地址正确这个结果(第一重是非管理员调用会报错,我们通过了;第二重是返回的结果和我们读插槽的结果一致)。虽然这里的admin函数是非view的,我们仍然直接从合约中得到了函数的返回结果(而不是得到了一个交易对象)。

再次重申一点:这个技巧只能针对那些特定的非view函数


四、另外一种阻止view函数的办法

有的时候可以故意将view函数写成非view不让你看数据的,非要给你制造一点麻烦,例如下面的合约:

/**
*Submitted for verification at Etherscan.io on 2021-06-29
*/

pragma solidity =0.6.6;
contract NoViewTest {
uint private _seed = 66;
function getSeed() external returns(uint) {
if (false) {
_seed = _seed;
}
return _seed;
}
}

通过一段永不执行的写操作让view 函数变成了 非 view 函数,这样编译的时候不会提示你该函数要定义为view函数。

那么我们可不可以使用上面的方式来直接调用该函数并得到结果66呢?

我们实际操作一下,笔者已经将该合约部署在kovan测试网上,地址为:https://kovan.etherscan.io/address/0xe00d0bc01f11dc5c5f66611a6cf37c3f3847fe1a#code

测试脚本为:noViewTest.js

const { ethers } = require("ethers");
const infura_key = "your_infura_key"
const provider = new ethers.providers.InfuraProvider("kovan",infura_key)
const contract_address = "0xe00d0bc01f11dc5c5f66611a6cf37c3f3847fe1a"
const abi = [
"function getSeed() view returns(uint)"
]
const test_contract = new ethers.Contract(contract_address,abi,provider)
async function getSeed() {
try {
let seed = await test_contract.getSeed()
console.log("seed:", seed.toString()) //66
}catch(e){
console.log(e)
}
}
getSeed()

我们运行node noViewTest.js,可以得到输出为66,验证我们上面提到的方法是有效的。

什么?还没有infura_key?赶快免费申请一个吧。


五、结束语

如果一个函数只是view函数,即不写数据,不改变状态(注:发log,发以太币也是属于改变状态),即使它加了调用权限并且设法弄成了非view函数(假装为写交易),我们都可以设法读取该函数的返回结果的。所以,还是公开透明吧!

当然,这是有前提的(比如别人会开源,或者通过浏览器验证公开代码,不让我们去猜代码)。

我们本次是链下使用脚本,那么,链上直接使用其它智能合约访问可否绕过这个限制呢?我们下次再尝试!当然有兴趣的读者可以自己先尝试一下。

在这里,笔者验证了一下,因为篇幅很小,所以直接加在后面了。

验证合约为:

pragma solidity =0.6.6;
contract NoViewTest {
uint private _seed = 66;
function getSeed() external returns(uint) {
if (false) {
_seed = _seed;
}
return _seed;
}
}
interface INoViewTest {
function getSeed() external view returns(uint);
}
contract NoViewCall {
INoViewTest public test = INoViewTest(0xE00D0BC01F11Dc5C5F66611A6cf37c3F3847fE1A);
function getSeed() external view returns(uint) {
return test.getSeed();
}
}

合约部署后的地址为:
https://kovan.etherscan.io/address/0x4A2b70Cab25E566AbF998Ed620e2C5CCA025d375#readContract
打开上述地址,直接点击getSeed,就能看到查询结果为66了。

在这里插入图片描述

希望本文章对以太坊和区块链学习(开发)者能提供一点点帮助。






推荐阅读
  • 波卡上最新的 Staking 介绍(下)
    波卡上最新的 Staking 介绍(下) ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文记录了作者对x265开源代码的实现与框架进行学习与探索的过程,包括x265的下载地址与参考资料,以及在Win7 32 bit PC、VS2010平台上的安装与配置步骤。 ... [详细]
  • AstridDAO 专访:波卡稳定币黑马 BAI
    加入Pol ... [详细]
  • 微服务下的几个难点问题及常见的解决方案
    原文链接:https:cloud.tencent.comdevelopernews1362051背景介绍1.1幂等性定义数学定义在数学里,幂等有 ... [详细]
  • 本文为转载,原连接:https:www.zhihu.comquestion40822826简单说一下吧做要解释这些要从netconf说起。netconf ... [详细]
  • Flow 生态案例学习 | Emerald City为Flow上DAO、教育和开发铺平道路
    原文链接:https://www.onflow.org/post/emer ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 配置IPv4静态路由实现企业网内不同网段用户互访
    本文介绍了通过配置IPv4静态路由实现企业网内不同网段用户互访的方法。首先需要配置接口的链路层协议参数和IP地址,使相邻节点网络层可达。然后按照静态路由组网图的操作步骤,配置静态路由。这样任意两台主机之间都能够互通。 ... [详细]
author-avatar
待续爱情2502861755
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有