13

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!

  • 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 7 '17 at 5:54
  • Related: ethereum.stackexchange.com/questions/15641/… – eth Apr 29 '17 at 10:26
13

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.

  • 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 7 '17 at 7: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
  • 5
    ~EXTCODESIZE` is usually zero in the constructor of a contract. You might want to check this out: reddit.com/r/ethereum/comments/916xni/… – Adibas03 Jul 23 '18 at 15:14
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.

  • 2
    Could you please elaborate more on your given solution? Thanks! – Tarek Jul 24 '18 at 0:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.