Este artículo describe tres métodos en Solidity para determinar si una dirección es un smart contract:
- Comprobar si
msg.sender == tx.origin. Este no es un método recomendado, pero debido a que muchos smart contracts lo utilizan, analizamos este método para mayor completitud. - El segundo (y método recomendado) es medir el tamaño del bytecode de la dirección utilizando
code.length. Este enfoque todavía presenta limitaciones que los desarrolladores deben sortear. - El tercero es el uso de
codehashy no está recomendado porque tiene las mismas limitaciones quecode.lengthcon complejidad adicional.
En este tutorial analizamos cada método. Finalmente, al terminar, proporcionamos algunos acertijos de Solidity para poner a prueba tu comprensión.
Método 1: Usar msg.sender == tx.origin para detectar si una dirección es un smart contract
La variable global tx.origin es la wallet que inició la transacción, mientras que msg.sender es la dirección que llamó al smart contract. Si una wallet llama a un smart contract directamente, entonces tx.origin será igual a msg.sender.
Sin embargo, supongamos que una wallet llama al smart contract A, el cual luego llama al smart contract B.
Desde la perspectiva del contract B, el msg.sender es el contract A y la wallet es tx.origin. Claramente, msg.sender no será igual a tx.origin dentro del contract B. El diagrama a continuación ilustra esta relación:

Al comprobar si msg.sender == tx.origin, el smart contract puede detectar si la llamada entrante proviene de un smart contract o de una wallet.
require(msg.sender == tx.origin) es un antipatrón
Usar un smart contract como wallet se está volviendo cada vez más popular con la adopción de account abstraction, como ERC-4337, y el uso de smart contracts para wallets multifirma (como Gnosis Safe).
Agregar require(msg.sender == tx.origin) a un smart contract significa que las wallets de account abstraction y las wallets multifirma no pueden interactuar con el smart contract.
Esta técnica solo puede probar si msg.sender es un contrato o no. No puede probar una dirección arbitraria.
Método 2: Detectar si una dirección es un smart contract con code.length
La forma recomendada para que un smart contract pruebe si una dirección es un smart contract es medir el tamaño de su bytecode.
Si una dirección tiene bytecode, entonces es un smart contract.
Considera el siguiente código:
contract TestAddress {
function test(
address target
)
public
view
returns (bool isContract) {
if (target.code.length == 0) {
isContract = false;
} else {
isContract = true;
}
}
}
Aunque todos los smart contracts tienen bytecode y ninguna dirección de wallet lo tiene, existen algunas consideraciones a tener en cuenta:
- Una dirección que no tiene bytecode ahora, podría tenerlo en el futuro si se despliega un smart contract en esa dirección.
- Usar
msg.sender.code.length == 0no es una forma confiable de detectar si una llamada entrante proviene de un smart contract. Si un smart contract realiza una llamada desde el constructor, entonces aún no ha desplegado su bytecode ymsg.sender.code.lengthserá 0. Mientras el constructor se está ejecutando, el bytecode del smart contract todavía no se ha desplegado. Por lo tanto,code.lengthserá cero. - En las cadenas EVM que soportan
selfdestruct, podría haber existido un smart contract entargeten el pasado, pero el smart contract se autodestruyó.
Probar msg.sender con code.length
Si una wallet llama a un contrato, entonces está garantizado que msg.sender.code.length será 0.
Si un contrato llama a otro contrato, entonces msg.sender.code.length será 0 si se llama desde el constructor y distinto de cero si se llama desde otra función del smart contract.
Probar una dirección (no msg.sender) con code.length
Si un smart contract utiliza la prueba address(target).code.length en algún target, y el target es un smart contract, entonces está garantizado que address(target).code.length será distinto de cero.
El desarrollador debe tener en cuenta que code.length podría convertirse en 0 más adelante si el contrato se autodestruye (asumiendo que la cadena soporta selfdestruct y el contrato tiene la capacidad de usar selfdestruct).
Si un smart contract utiliza la prueba address(target).code.length en algún target, y el target es una wallet, entonces está garantizado que address(target.code.length) será 0.
Sin embargo, solo porque address(target).code.length sea 0 ahora, no significa que siempre será cero. Un smart contract podría ser desplegado allí más adelante. Supongamos que te doy una dirección. La mides ahora con address(target).code.length y devuelve 0. Esa medición será precisa en el momento en que la realizaste, pero es posible que yo pueda desplegar un contrato en esa dirección (target) en una fecha posterior y, si la vuelves a medir con address(target).code.length, será distinta de cero.
Caso de uso común para comprobar si una dirección es un smart contract
Si un token se transfiere a un smart contract que no tiene la funcionalidad para enviar los tokens hacia afuera, entonces los tokens quedarán atrapados y serán propiedad de ese contrato para siempre.
Como tal, algunos estándares de tokens toman medidas para evitar que esto suceda.
El ERC-721 con la función safeTransferFrom, por ejemplo, comprobará si la dirección a la que se transfiere es un smart contract (usando el truco de code.length).

Si lo es, intentan llamar a una función especial en el contrato para preguntar si dicho contrato soporta tokens ERC-721. Si la función no está allí, entonces sabe que los tokens quedarán atrapados y bloquea la transferencia.
Método 3: Codehash es una mala forma de probar si una dirección es un contrato
El codehash devuelve el keccak256 del bytecode de una dirección.
Tiene el siguiente comportamiento:
- Si la dirección no tiene balance de Ethereum y no tiene bytecode, no hay nada que hashear y devuelve
bytes32(0). - Si la dirección tiene un balance de Ethereum pero no tiene bytecode, devuelve el keccak256 de datos vacíos
keccak256("")que equivale a0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470. - Si la dirección tiene bytecode (independientemente del balance) devuelve el
keccak256del bytecode del contrato.
El comportamiento exacto de codehash se describe en los comentarios del cliente de Ethereum sobre codehash.
Algunos contratos han utilizado erróneamente el codehash para probar si una dirección tiene bytecode o no. Esta no es una buena idea porque si usamos codehash en un contrato que no tiene bytecode, obtendremos bytes32(0) o keccak256("") y debemos comprobar ambas posibilidades.
Si una dirección a no tiene bytecode y no tiene ether, entonces address(a).codehash devuelve bytes32(0) o 32 bytes de puros ceros. Sin embargo, si alguien transfiere Ether a la dirección, entonces el codehash se convertirá en keccak256(""), a pesar de no ser ni una wallet ni un smart contract.
Puedes probar el código a continuación en Remix para ver el comportamiento de codehash:
contract TestHash {
function getHash()
external
view
returns (bytes32) {
// random address with no balance or code
return address(101).codehash;// returns 0x000...000
}
function hashOfNonEmptyWallet()
external
view
returns (bytes32) {
// tx.origin has a non-zero ether balance
return tx.origin.codehash;
// returns a non-zero hash
}
// observe that `keccakNil` and `hashOfNonEmptyWallet`
// return the same value
function keccakNil()
external
pure
returns (bytes32) {
return keccak256("");
}
// Deploy SomeTestContract and put its address in
// codeHashOtherContract to test it
function codeHashOtherContract(
address _a
)
external
view
returns (bool) {
// returns true because the codehash
// of another contract
// is equal to the `keccak256` of its bytecode
return a.codehash == keccak256(a.code);
}
}
contract SomeTestContract {
function someFunction()
external
pure
returns (uint256) {
return 5;
}
}
Tanto codehash como code.length pueden ser utilizados para determinar si una dirección es un smart contract comprobando la presencia de bytecode; sin embargo, codehash introduce una complejidad innecesaria al hashear el bytecode, lo que da como resultado tres posibles desenlaces, mientras que solo necesitamos comprobar si code.length es cero o no.
Es mucho más simple comprobar code.length.
Acertijo para poner a prueba tus conocimientos
Acertijo 1
¿Puedes lograr que el siguiente contrato devuelva true cuando se llame a puzzle y que no se revierta?
contract Puzzle {
function puzzle()
external
view
returns (bool success) {
require(msg.sender != tx.origin);
require(msg.sender.code.length == 0);
success = true;
}
}
Acertijo 2
¿Qué debería devolver tx.origin.code.length? ¿Devuelve siempre el mismo valor?
Aprende más con RareSkills
Consulta nuestro curso de Solidity si eres nuevo en Solidity. Consulta nuestro bootcamp de Solidity si ya tienes algo de experiencia. ¡Gracias por leer!
Publicado originalmente el 5 de abril de 2024