在Solidity中,地址可以是账户、合约(或者其他一些东西,例如交易)。当我有一个变量x,保存了一个地址,我该如何测试它是否为合约?
(是的,我已经阅读了文档中关于类型的章节)
在Solidity中,地址可以是账户、合约(或者其他一些东西,例如交易)。当我有一个变量x,保存了一个地址,我该如何测试它是否为合约?
(是的,我已经阅读了文档中关于类型的章节)
是的,您可以通过使用一些EVM汇编代码来获取地址的代码大小:
function isContract(address addr) returns (bool) {
uint size;
assembly { size := extcodesize(addr) }
return size > 0;
}
如果该函数从合约的构造函数中调用(因为合约尚未部署),它将返回false。
应非常小心地使用此代码,以避免安全漏洞,例如:
https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide (存档)
如重复强调:
不要使用EXTCODESIZE检查来防止智能合约调用函数。这是不可靠的,它可以通过构造函数调用来破坏,因为当构造函数运行时,该地址的EXTCODESIZE返回0。
请参见示例代码,了解欺骗EXTCODESIZE返回0的合约。
如果您想确保一个EOA正在调用您的合约,简单的方法是require(msg.sender == tx.origin)
。但是,防止一个合约是一种反模式,会涉及到安全性和互操作性问题。具体请参考此处以及这里和这里。
当账户抽象化实现时,require(msg.sender == tx.origin)
需要重新审视。
正如@Luke在评论中指出的那样,没有通用的链上方法可以了解有关被调用方的信息。如果要“调用”地址,则没有一般性的方法可用于确定该地址是合约、EOA还是可部署新合约的地址,或者它是否是CREATE2地址。
有一种针对某些被调用方的非通用方法:您可以在链上拥有一个映射,存储已知的EOA或合约地址。(请记住,对于没有任何链上历史记录的地址,您无法知道它是EOA还是可以部署合约的地址。)
require(msg.sender == tx.origin)
只能检测函数调用者是否为 EOA,不能用于检测任何其他第三方合约是否为 EOA(例如您想从自己的函数中调用的合约)。 - Luke Hutchison<address>.code.size
(不需要汇编语言)。人们需要认识到这种形式。(我可能有语法错误,因为我现在离电脑很远。) - Luke Hutchison你不能在Solidity合约内查询此信息,但是如果你只想知道一个地址是否拥有合约代码,你可以使用geth控制台或类似工具进行检查,例如:
> eth.getCode("0xbfb2e296d9cf3e593e79981235aed29ab9984c0f")
使用十六进制字符串(这里是0xbfb2e296d9cf3e593e79981235aed29ab9984c0f
)作为您要查询的地址。这将返回存储在该地址处的字节码。
您也可以使用区块链扫描器找到该地址处合约的源代码,例如在etherscan.io上显示的ecsol库。
const Web3 = require('web3')
// make sure you are running geth locally
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
is_contract = async function(address) {
res = await web3.eth.getCode(address)
return res.length > 5
}
is_contract('your address').then(console.log)
return res.length > 5
?如果这不是一个智能合约,那么 res
不应该是 0x
吗?这意味着 res.length > 2
也同样有效?我想你也可以测试 res.startsWith("0x6080604052")
? - Matthew Scerri从openzeppeling的Address.sol库中,它有这个函数:
pragma solidity ^0.8.1;
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
isContract
函数对以下类型的地址将返回false:
外部拥有的账户
正在构建中的合约
将要创建合约的地址
曾经存在过的合约地址,但已被销毁
如果您掌握了相关信息,您可以做什么。 如果交易发送者地址为空或未被占用,则可以确定该地址是合约账户还是EOA(外部拥有的账户)。 例如,在网络上发送创建合约交易时,交易中的接收地址为空/未使用。
参考自Github: https://github.com/ethereum/go-ethereum/wiki/Contracts-and-Transactions
希望这能帮到您。
require(tx.origin == msg.sender);
tx.origin是指发起此连续函数调用的原始地址的引用,而msg.sender是直接调用目标函数的地址。这意味着,tx.origin必须是人类,而msg.sender可以是合约或人类。因此,如果有人从合约中调用您,那么msg.sender就是一个合约地址,与tx.origin不同。
我知道大多数合约可能会使用@Manuel Aráoz的代码,在大多数情况下都能正常工作。但是,如果您在合约的构造函数中调用函数,则extcodesize将返回0,这将导致isContract检查失败。
注意:如果您不清楚tx.origin代表什么,请勿在其他情况下使用它。
(tx.origin == msg.sender)
,则意味着没有函数调用链。 - Yilmaz