¿Cómo averigua un contrato si otra dirección es un contrato?

¿Es posible, desde dentro de un contrato escrito en Solidity, verificar si un contrato está colocado en una dirección específica o si esta dirección no contiene ningún código?

Respuestas (7)

Esto funciona:

function isContract(address _addr) private returns (bool isContract){
  uint32 size;
  assembly {
    size := extcodesize(_addr)
  }
  return (size > 0);
}

El lenguaje ensamblador en el que se compilan todos los contratos de Ethereum contiene un código de operación para esta operación precisa: EXTCODESIZE. Este código de operación devuelve el tamaño del código en una dirección. Si el tamaño es mayor que cero, la dirección es un contrato. Pero debe escribir el código ensamblador dentro del contrato para acceder a este código de operación, ya que el compilador de Solidity no lo admite directamente en este momento. El código anterior crea un método privado al que puede llamar desde su contrato para verificar si otra dirección contiene código. Si no desea un método privado, elimine la palabra clave privada del encabezado de la función.

Editar: EXTCODESIZEdevuelve 0 si se llama desde el constructor de un contrato. Entonces, si está usando esto en un entorno sensible a la seguridad, debería considerar si esto es un problema.

Que yo sepa, este es el mejor método que tenemos para verificar si una dirección es un contrato en este momento. Pero cualquiera que use esta solución también debe saber que es posible que un contrato devuelva 0 EXTCODESIZEsi la llamada a la función se realizó desde dentro de ese constructor de contratos. Esto significa que no puede usar ciegamente EXCODESIZEpara evitar que otros contratos interactúen con el suyo, debe usarse con precaución.
Originalmente necesitaba esto ya que había creado mis propios contratos de tokens ERC20 y algunos contratos financieros que transferían estos tokens. Cuando se liquidaban los contratos financieros, se eliminaban llamando a selfdestruct. Como medida de seguridad, no quería que el ERC20 transfiriera fondos a contratos financieros eliminados, así que introduje este cheque. Para este caso de uso, esta verificación funciona, pero para otros casos de uso, se debe tener en cuenta la advertencia de Rob Hitchens B9lab.
Después de 0.8.1, también se puede usar este fragmento sin pérdida de eficiencia (aunque aún se debe tener la misma precaución antes mencionada):function isContract(address addr) private returns (bool isContract) { return addr.code.length > 0; }
+1 ¡Gracias! Veo esto en el contrato WUST en la cadena BSC, y me pregunto. Para referencia de uso real en proyectos reales bscscan.com/address/…

Todo el crédito para @AnAllergyToAnalogy por el elemento de precaución.

Hice un ejemplo para demostrar que a constructorengañará a este método. Publicación para otros que puedan encontrarse con este hilo.

En la práctica, isContractno puede detectar de manera confiable a un atacante que llama desde un 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();
    }
}
  • desplegar victima
  • implementar el atacante con la dirección de la víctima
  • comprobar iTrickedIten atacante

Espero eso ayude.

ACTUALIZAR

Address.solen OpenZeppelin/contracts/utility tiene una función isContractque funciona según el principio descrito. Se aplica la misma advertencia. Utilizar con conciencia.

Como dice @eth a continuación, podemos usar require(msg.sender == tx.origin). Muchos dicen que tiene vulnerabilidades de seguridad. Estoy seguro de que sería para otros casos. Pero en mi caso, solo quiero saber si la persona que llama tiene contrato o no. No quiero que otros contratos interactúen con mi contrato en absoluto. Entonces, ¿es seguro usar esto, solo para este caso de uso? ¡Gracias nuevamente de antemano por su tiempo!
La respuesta es muy buena, en mi opinión. También la fuente es muy respetablr. Yo haría caso a las advertencias.

Dado que esto está relacionado con la seguridad, es útil enfatizar https://stackoverflow.com/questions/37644395/how-to-find-out-if-an-ethereum-address-is-a-contract :

Se descubrió que la respuesta más votada con la isContractfunción que usa EXTCODESIZE era pirateable.

La función devolverá falso si se invoca desde el constructor de un contrato (porque el contrato aún no se ha implementado).

El código debe usarse con mucho cuidado, si es que se usa, para evitar ataques de seguridad como:

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

Para repetir :

No utilice la verificación EXTCODESIZE para evitar que los contratos inteligentes llamen a una función. Esto no es infalible, puede ser subvertido por una llamada de constructor, debido al hecho de que mientras el constructor se está ejecutando, EXTCODESIZE para esa dirección devuelve 0.

Consulte el código de muestra de un contrato que engaña a EXTCODESIZE para que devuelva 0.


Si quiere asegurarse de que un EOA está llamando a su contrato, una forma simple es require(msg.sender == tx.origin). Sin embargo, impedir un contrato es un antipatrón con consideraciones de seguridad e interoperabilidad .

Esto deberá revisarse cuando se implemente la abstracción de cuenta.

Estoy trabajando en esto también. Hay un código de operación llamado extcodehash. dice _

El EXTCODEHASH de la cuenta sin código es c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 cuál es el hash keccack256 de datos vacíos

Entonces creo que existe la posibilidad de verificar isContractusando esto extcodehashcombinado con elc5d2460186f72...

function isContract(address addr) internal view returns (bool) {
    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

    bytes32 codehash;
    assembly {
        codehash := extcodehash(addr)
    }
    return (codehash != 0x0 && codehash != accountHash);
}

Esto significa que, si el hash del código no es igual a 0 ni c5d2460186f72..., ¿podemos concluir que esta es la dirección del contrato?

Necesitaríamos una prueba para ver si podemos engañar a su enfoque con un constructor.

Un punto obvio que no se enfatizó en las respuestas publicadas anteriormente es que, SÍ, requiere

assembly {
size := extcodesize(_addr)
}

garantizará que solo un contrato puede pasar el cheque si size > 0.

Sin embargo, la verificación opuesta para ver que un remitente NO es un contrato (sino un EOA) es mucho más complicada y requiere un esquema de verificación de firma para probar. Hay 2 enfoques, que puede leer más aquí y aquí .

Actualización para Solidity v0.8 y superior

pragma solidity >=0.8.0;

function isContract(address _addr) private returns (bool isContract) {
    isContract = _addr.code.length > 0;
}

Tenga en cuenta que todas las advertencias de seguridad mencionadas en los otros comentarios aún se aplican.

La mejor manera de verificar y asegurarse de que una dirección no sea un contrato es comparando tx.origin con msg.sender. Podrías hacer un modificador para eso.

    modifier onlyEoa() {
        require(tx.origin == msg.sender, "Not EOA");
        _;
    }

address.code.length > 0no siempre es una buena solución porque si un contrato llama a su contrato desde su constructor, entonces address.code.lengthserá 0 porque el contrato atacante aún no se ha construido, engañándolo para que piense que no es un contrato.

Más información en esta respuesta: ¿Hay alguna función simple realizada en Solidity para verificar si una dirección es una dirección de contrato o una dirección de billetera?