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

8

39

An address in Solidity can be an account or a contract (or other things, such as a transaction). When I have a variable x, holding an address, how can I test if it is a contract or not?

(Yes, I've read the chapter on types in the doc)

Travertine answered 5/6, 2016 at 16:48 Comment(0)
T
13

Edit: Solidity has changed since this answer was first written, @manuel-aráoz has the correct answer.

There is no way in solidity to check if an address is a contract. One of the goals of Ethereum is for humans and smart contracts to both be treated equally. This leads into a future where smart contracts interact seamlessly with humans and other contracts. It might change in the future , but for now an arbitrary address is ambiguous.

Tenotomy answered 7/6, 2016 at 4:27 Comment(1)
The gas consumed by sending to contract is utterly different than the gas consumed by sending to an address. If there were a goal to treat those two things in the same way, there couldn't be a gas distinction.Fawnia
H
62

Yes you can, by using some EVM assembly code to get the address' code size:

function isContract(address addr) returns (bool) {
  uint size;
  assembly { size := extcodesize(addr) }
  return size > 0;
}
Halcomb answered 2/12, 2016 at 19:8 Comment(2)
Here's some info on how this function worksBeckybecloud
This code is dangerous and no longer advisable as it is hackable because EXTCODESIZE returns 0 in a contract's constructor.Kevin
K
34

The top-voted answer with the isContract function that uses EXTCODESIZE was discovered to be hackable.

The function will return false if it is invoked from a contract's constructor (because the contract has not been deployed yet).

The code should be used very carefully, if at all, to avoid security hacks such as:

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

To repeat:

Do not use the EXTCODESIZE check to prevent smart contracts from calling a function. This is not foolproof, it can be subverted by a constructor call, due to the fact that while the constructor is running, EXTCODESIZE for that address returns 0.

See sample code for a contract that tricks EXTCODESIZE to return 0.


Checking if a caller is a contract

If you want to make sure that an EOA is calling your contract, a simple way is require(msg.sender == tx.origin). However, preventing a contract is an anti-pattern with security and interoperability considerations.

require(msg.sender == tx.origin) will need revisiting when account abstraction is implemented.

Checking if a callee is a contract

As @Luke points out in a comment, there is no general on-chain way to find out about a callee. If you want to "call" an address, there's no general way to find out if that address is a contract, EOA, or an address that a new contract can be deployed on, or if it's a CREATE2 address.

One non-general way that works for some callees: you can have a mapping on-chain that stores addresses of known EOAs or contracts. (Just remember that for an address without any on-chain history, you can't know if it's an EOA or an address that a contract can be deployed on.)

Kevin answered 5/1, 2019 at 22:26 Comment(3)
It should probably be pointed out that require(msg.sender == tx.origin) only detects if the caller of a function is an EOA, it can't be used to detect if any other third-party contract is an EOA (such as a contract that you want to call from your own function).Westley
@LukeHutchison Upvoted, great point! Added the caller and callee case; happy to hear from you if I missed some things or you have other suggestions.Kevin
For completeness you could add that extcodesize is now abstracted away to <address>.code.size in solidity (no assembly needed). People need to recognize this form too. (I might have the syntax wrong, I'm not near a computer right now.)Westley
A
14

This isn't something you can query from within a contract using Solidity, but if you were just wanting to know whether an address holds contract code or not, you can check using your geth console or similar with eg:

  > eth.getCode("0xbfb2e296d9cf3e593e79981235aed29ab9984c0f")

with the hex string (here 0xbfb2e296d9cf3e593e79981235aed29ab9984c0f) as the address you wish to query. This will return the bytecode stored at that address.

You can also use a blockchain scanner to find the source code of the contract at that address, for example the ecsol library as shown on etherscan.io.

Augusto answered 24/11, 2016 at 15:42 Comment(0)
T
13

Edit: Solidity has changed since this answer was first written, @manuel-aráoz has the correct answer.

There is no way in solidity to check if an address is a contract. One of the goals of Ethereum is for humans and smart contracts to both be treated equally. This leads into a future where smart contracts interact seamlessly with humans and other contracts. It might change in the future , but for now an arbitrary address is ambiguous.

Tenotomy answered 7/6, 2016 at 4:27 Comment(1)
The gas consumed by sending to contract is utterly different than the gas consumed by sending to an address. If there were a goal to treat those two things in the same way, there couldn't be a gas distinction.Fawnia
I
3

From openzeppeling Address.sol library, it has this function:

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 will return false for the following types of addresses:

  • an externally-owned account

  • a contract in construction

  • an address where a contract will be created

  • an address where a contract lived, but was destroyed

Implausibility answered 20/11, 2022 at 20:10 Comment(0)
T
2

If you want to use nodejs to confirm, you can do this:

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)
Tabloid answered 15/2, 2022 at 6:40 Comment(1)
I don't think it makes much of a difference, but I'm curious as to why you went with return res.length > 5? If it's not a Smart contract shouldn't res be 0x, meaning res.length > 2 should work just as well? I guess you can also test for res.startsWith("0x6080604052")?Philomel
C
-1

What you can do, granted you have the information at hand. If the transactions sender address was null or unoccupied then you can tell if the address is a contract account or an EOA (externally owned account). i.e. when sending a create contract transaction on the network then the receive address in the transaction is null/not used.

Reference from github: https://github.com/ethereum/go-ethereum/wiki/Contracts-and-Transactions

Hope this helps.

Caspar answered 28/6, 2016 at 21:6 Comment(1)
This link is clearly outdated. It's 2021, this answer is from 2016.Caspar
A
-1

If you are checking whether the caller is an EOA rather than a contract:

Short answer:

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

tx.origin is a reference of the original address who initiates this serial function call, while msg.sender is the address who directly calls the target function. Which means, tx.origin must be a human, msg.sender can be a contract or human. Thus, if someone calls you from a contract, then the msg.sender is a contract address which is different from tx.origin.

I know most contracts may use @Manuel Aráoz's code, which works in most cases. But if you call a function within the constructor of a contract, extcodesize will return 0 which fails the isContract check.

NOTE: DON'T use tx.origin under other circumstances if you are not clear about what it represents because .

Agnesagnese answered 26/10, 2018 at 6:47 Comment(1)
this means that direct user is calling the function. this is one way of mitigating reentrancy attack because if (tx.origin == msg.sender) means there is not a chain of function callsImplausibility

© 2022 - 2024 — McMap. All rights reserved.