20

My problem is that I need to make sure my contract processes only the transactions coming from EOA (externally owned accounts). For this I need to determine the type of account I'm dealing with. In this question I found a solution, here's the code:

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

Since I don't understand assembly at all, I have no idea what's going on there.

  • Does this function work as described?
  • Is there a way for a contract to fool it?
  • Will this function work when the network transitions to POS?

Thank you!

eth
  • 85,679
  • 53
  • 285
  • 406
manidos
  • 4,298
  • 3
  • 31
  • 55
  • A separate question explaining why "I need to make sure my contract processes only the transactions coming from EOA" may also be helpful. In most cases, it is not recommended to discriminate against contracts because your system won't be as flexible and interoperable. For example, blockchains can be helpful with IoT because they can allow "things" to make and receive payments. – eth Apr 07 '17 at 05:54
  • Related: https://ethereum.stackexchange.com/questions/15641/how-does-a-contract-find-out-if-another-address-is-a-contract – eth Apr 29 '17 at 10:26

2 Answers2

18

EDIT: 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.


If you want to make sure that an externally owned account (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.

This will need revisiting when account abstraction is implemented.


Prior answer.

Yes, the function works.

EXTCODESIZE is the EVM opcode for getting the size of the code at an address.

0x3b EXTCODESIZE Get size of an account's code

A contract cannot fool EXTCODESIZE to return zero for the contract's size.

Also, an externally owned account (EOA) cannot fool this code into making it think that the EOA is a contract. This is because an EOA has no code (zero length) and you can't put code into an EOA any more feasibly than it is to find the private key of a contract.

The function does not have any dependency on Proof of Stake and will continue working.

Jake Lin
  • 103
  • 3
eth
  • 85,679
  • 53
  • 285
  • 406
  • This does not depend on POS, but it will change due to account abstraction in Serenity. I would not rely on this continuing to work forever, and agree that there is essentially no reason why it should matter whether an account is a contract or not – Tjaden Hess Apr 07 '17 at 07:00
  • @TjadenHess Which part will change? Or should this be a separate question? It seems reliable that the definition of a contract would be an account that has code... – eth Apr 29 '17 at 10:26
  • In Serenity, the idea is that every account will be a contract, i.e. normal accounts will have ECDSA verification code – Tjaden Hess Apr 29 '17 at 14:57
  • https://blog.ethereum.org/2015/12/24/understanding-serenity-part-i-abstraction/ – Tjaden Hess Apr 29 '17 at 15:03
  • 5
    ~EXTCODESIZE` is usually zero in the constructor of a contract. You might want to check this out: https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide/?__twitter_impression=true – Adibas03 Jul 23 '18 at 15:14
  • This answer is incorrect, see PhABC's answer below. I wonder how many developers have been misguided into writing vulnerable contracts because of this post. – Daniel Que Jul 23 '18 at 18:24
  • @Adibas03 and DanielQue Thanks for the alarm and answer updated. This won't be the last time that a hacker will find a flaw in a technique. – eth Jul 24 '18 at 07:39
  • @TjadenHess the Serenity blog was posted almost 3 years ago. As far as I know, EOAs are still in play. Has the implementation of only one account type been abandoned? I would like to restrict my function to calls from external contracts only (no EOAs), but if extcodesize will soon return non-zero values for what was once external accounts, I need to avoid this shortcut. – Garen Vartanian Oct 02 '18 at 17:35
  • Account abstraction is still in the works, you can see a more recent discussion here: https://ethresear.ch/t/a-recap-of-where-we-are-at-on-account-abstraction/1721 – Tjaden Hess Oct 03 '18 at 15:10
8

This code snippet is correct, although it seems important to note that EXTCODESIZE will return 0 when called within the constructor of a contract, since the contract is not created yet. Hence, if msg.sender is a contract, the isContract() modifier could return false if your contract's function is called within the constructor of the msg.sender contract. This attack vector has been used on various occasions, such as draining the FoMo3D's ETH "airdrop pot".

A solution to this would be to (A) require msg.sender to provide an ECDSA signature for their first function call, (B) require msg.sender to call your contract on two separate blocks or (C) add require(msg.sender == tx.origin). The latter would be the cheapest, followed by ECDSA and the most expensive would be the 2 step process.

eth
  • 85,679
  • 53
  • 285
  • 406
PhABC
  • 512
  • 5
  • 10