Is it possible, from within a contract written in Solidity, to check if a contract is placed on a specific address or if this address does not contain any code?
7 Answers
This works:
function isContract(address _addr) private returns (bool isContract){
uint32 size;
assembly {
size := extcodesize(_addr)
}
return (size > 0);
}
The assembly language that all Ethereum contracts compile down to contains an opcode for this precise operation: EXTCODESIZE. This opcode returns the size of the code on an address. If the size is larger than zero, the address is a contract. But you need to write assembly code within the contract to access this opcode since the Solidity compiler does not support it directly at the moment. The above code creates a private method that you can call from within your contract to check if another address contains code. If you don't want a private method, remove the private keyword from the function header.
Edit: EXTCODESIZE returns 0 if it is called from the constructor of a contract. So if you are using this in a security sensitive setting, you would have to consider if this is a problem.
- 4,220
- 2
- 16
- 38
Full credit to @AnAllergyToAnalogy for the caution item.
I made an example to demonstrate that a constructor will trick this method. Posting for others who might come across this thread.
In practice, isContract can't reliably detect an attacker calling from a constructor.
pragma solidity 0.4.25;
contract Victim {
function isContract() public view returns(bool){
uint32 size;
address a = msg.sender;
assembly {
size := extcodesize(a)
}
return (size > 0);
}
}
contract Attacker {
bool public iTrickedIt;
Victim v;
constructor(address _v) public {
v = Victim(_v);
// addrss(this) doesn't have code, yet
iTrickedIt = !v.isContract();
}
}
- deploy Victim
- deploy Attacker with Victim address
- check
iTrickedItin Attacker
Hope it helps.
UPDATE
Address.sol in OpenZeppelin/contracts/utility has function isContract that works on the principle described. Same warning applies. Use with awareness.
- 55,151
- 11
- 89
- 145
-
1As @eth below says, we can use require(msg.sender == tx.origin). Many says it has security vulnerabilities. I am sure it would be for other cases. But in my case, I just want to know caller is contract or not. I dont want other contracts from interacting with my contract whatsoever. so is it safe to use this, just for this use case? Thank again in advance for your time! – Yogesh - EtherAuthority.io Mar 24 '19 at 15:23
-
1The answer is very good, in my opinion. Also the source is very respectablr. I would heed the warnings. – Rob Hitchens Mar 24 '19 at 17:00
Since this is security related, it's helpful to emphasize https://stackoverflow.com/questions/37644395/how-to-find-out-if-an-ethereum-address-is-a-contract:
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.
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.
This will need revisiting when account abstraction is implemented.
- 85,679
- 53
- 285
- 406
Update for Solidity v0.8 and Above
pragma solidity >=0.8.0;
function isContract(address _addr) view returns (bool) {
return _addr.code.length > 0;
}
Note that all security caveats mentioned in the other comments still apply.
- 17,902
- 6
- 73
- 143
I'm working on this as well. There is an opcode called extcodehash. It says
The EXTCODEHASH of the account without code is c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 what is the keccack256 hash of empty data
So I think there is a possibility to check isContract by using this extcodehash combined with the c5d2460186f72...
function isContract(address addr) internal view returns (bool) {
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
bytes32 codehash;
assembly {
codehash := extcodehash(addr)
}
return (codehash != 0x0 && codehash != accountHash);
}
This means, if the code hash does not equal to 0 nor c5d2460186f72..., we can conclude this is the contract address?
- 265
- 2
- 6
-
We would need a test to see if we can trick your approach with a constructor. – Sky Jul 11 '22 at 06:06
One obvious point not emphasized in previously posted answers is that, YES, requiring
assembly {
size := extcodesize(_addr)
}
will guarantee that only a contract can make it past the check if size > 0.
However, the opposite check to see that a sender is NOT a contract (but an EOA) is much more complicated and requires a signature verification scheme in order to prove. There are 2 approaches, which you can read up on more here and here.
- 483
- 3
- 14
The best way to check and make sure that an address is not a contract, is by comparing tx.origin with msg.sender. You could do a modifier for that.
modifier onlyEoa() {
require(tx.origin == msg.sender, "Not EOA");
_;
}
address.code.length > 0 is not always a good solution because if a contract calls your contract from its constructor, then address.code.length will be 0 because the attacking contract has not been constructed yet, tricking you into thinking that is not a contract.
More info in this response: Is there any simple function done in Solidity to check if an address is a contract address or a wallet address?
- 4,599
- 3
- 5
- 28
EXTCODESIZEif the function call was made from within that contracts constructor. This means that you can't blindly useEXCODESIZEto prevent other contracts from interacting with yours, it must be used with caution. – AnAllergyToAnalogy Aug 18 '18 at 13:20selfdestruct. As a security measure, I did not want the ERC20 to be transfer funds to deleted financial contracts so I introduced this check. For this use case, this check works but for other use cases, the Rob Hitchens B9lab warning should be heeded. – Thorkil Værge Feb 02 '19 at 14:29
– shamisen Jan 03 '22 at 12:55function isContract(address addr) private returns (bool isContract) { return addr.code.length > 0; }