如何确定以太坊地址是否是合约?

How to find out if an Ethereum address is a contract?

Solidity 中的地址可以是账户或合约(或其他事物,例如交易)。当我有一个变量x,持有一个地址,我如何测试它是否是一个合约?

(是的,我已经阅读了文档中的 chapter on types

编辑:自从第一次写这个答案以来,Solidity 发生了变化,@manuel-aráoz 有正确的答案。

无法可靠地检查地址是否为合约。以太坊的目标之一是让人类和智能合约得到平等对待。这导致智能合约与人类和其他合约无缝交互的未来。将来可能会改变,但目前任意地址是不明确的。

只要手头有信息,您可以做什么。 如果交易发件人地址为空或未被占用,那么您可以判断该地址是合约账户还是 EOA(外部拥有账户)。 即当在网络上发送创建合约交易时,交易中的接收地址被 null/not 使用。

参考自github: https://github.com/ethereum/go-ethereum/wiki/Contracts-and-Transactions

希望对您有所帮助。

这不是您可以使用 Solidity 从合同中查询的内容,但如果您只是想知道地址是否包含合同代码,您可以使用 geth 控制台或类似工具进行检查,例如:

  > eth.getCode("0xbfb2e296d9cf3e593e79981235aed29ab9984c0f")

用十六进制字符串(这里是0xbfb2e296d9cf3e593e79981235aed29ab9984c0f)作为你要查询的地址。这将 return 存储在该地址的字节码。

您也可以使用区块链扫描器在该地址找到合约的源代码,例如the ecsol library as shown on etherscan.io

是的,你可以,通过使用一些 EVM 汇编代码来获取地址的代码大小:

function isContract(address addr) returns (bool) {
  uint size;
  assembly { size := extcodesize(addr) }
  return size > 0;
}

如果您要检查来电者是否是 EOA 而不是合同:

简答:

require(tx.origin == msg.sender);

tx.origin是发起这个串口函数调用的原地址的引用,而msg.sender是直接调用目标函数的地址。这意味着,tx.origin 必须是人,msg.sender 可以是合同或人。因此,如果有人通过合约给你打电话,那么 msg.sender 是一个不同于 tx.origin.

的合约地址

我知道大多数合同可能会使用@Manuel Aráoz 的代码,在大多数情况下都可以使用。但是,如果您在合约的构造函数中调用一个函数,extcodesize 将 return 0 未通过 isContract 检查。

注意:如果您不清楚它代表什么,请不要在其他情况下使用 tx.origin,因为 .

使用 EXTCODESIZE 的 isContract 函数的投票最高的答案被发现是可破解的。

如果从合约的构造函数调用该函数(因为合约尚未部署),该函数将 return false。

应该非常小心地使用代码,如果有的话,以避免安全黑客攻击,例如:

https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide (archive)

repeat

不要使用 EXTCODESIZE 检查来防止智能合约调用函数。这不是万无一失的,它可以通过构造函数调用来破坏,因为虽然构造函数是 运行,但该地址的 EXTCODESIZE returns 0.

参见 sample code 将 EXTCODESIZE 设置为 return 0 的合约。


检查调用者是否是合同

如果你想确保 EOA 正在调用你的合约,一个简单的方法是 require(msg.sender == tx.origin)。但是,阻止合同是一个 anti-pattern with security and interoperability 考虑因素。

require(msg.sender == tx.origin) 实施帐户抽象化后需要重新访问。

检查被调用者是否是合约

正如@Luke 在评论中指出的那样,没有 通用的链上方式来了解被调用者。如果你想“调用”一个地址,没有通用的方法来确定该地址是合约、EOA 还是可以部署新合约的地址,或者它是否是 CREATE2 地址。

一种适用于某些被调用方的非通用方法:您可以在链上创建一个映射,用于存储已知 EOA 或合约的地址。 (请记住,对于没有任何链上历史记录的地址,您无法知道它是 EOA 还是可以部署合约的地址。)

如果想用nodejs来确认,可以这样做:

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)