Diferencia entre requerir y afirmar y la diferencia entre revertir y lanzar

Estaba mirando los documentos y estoy buscando una aclaración sobre la diferencia entre requirey asserty throwy revert.

afirmar (condición bool): cancelar la ejecución y revertir los cambios de estado si la condición es falsa (uso para error interno)

require (condición booleana): cancela la ejecución y revierte los cambios de estado si la condición es falsa (uso para entrada mal formada)

Específicamente con respecto a asserty require, ¿cómo traza la línea entre una entrada mal formada y un error interno?

También puede consultar este excelente artículo sobre el tema: Revert(), Assert() y Require() en Solidity, y el nuevo código de operación REVERT en EVM

Respuestas (5)

Hay dos aspectos a considerar al elegir entre assert()yrequire()

  1. Eficiencia de gases
  2. Análisis de código de bytes

1. Eficiencia de gases

assert(false)compila a 0xfe, que es un código de operación no válido, consume todo el gas restante y revierte todos los cambios.

require(false)compila 0xfdcuál es el REVERTcódigo de operación, lo que significa que reembolsará el gas restante. El código de operación también puede devolver un valor (útil para la depuración), pero no creo que sea compatible con Solidity en este momento. (2017-11-21)

2. Análisis de código de bytes

De los documentos (énfasis mío)

La función require debe usarse para garantizar que se cumplan condiciones válidas, como entradas o variables de estado del contrato, o para validar valores de retorno de llamadas a contratos externos. Si se usan correctamente, las herramientas de análisis pueden evaluar su contrato para identificar las condiciones y llamadas de función que llegarán a una aserción fallida. El código que funciona correctamente nunca debe llegar a una declaración de afirmación fallida; si esto sucede, hay un error en su contrato que debe corregir.

El extracto anterior es una referencia al todavía (a partir del 2017-11-21) experimental e indocumentado SMTChecker.

Utilizo algunas heurísticas para ayudarme a decidir cuál usar.

Usar require()para:

  • Validar las entradas del usuario
  • Validar la respuesta de un contrato externo,
    es decir. usarrequire(external.send(amount))
  • Valide las condiciones de estado antes de ejecutar operaciones de cambio de estado, por ejemplo, en una ownedsituación de contrato
  • En general, debe usar requiremás a menudo,
  • Generalmente, se utilizará hacia el comienzo de una función.

Usar assert()para:

  • compruebe si hay desbordamiento/subdesbordamiento
  • verificar invariantes
  • validar el estado del contrato después de hacer cambios
  • evitar condiciones que nunca, nunca deberían ser posibles.
  • En general, debe usar assertmenos a menudo
  • Generalmente, se usará hacia el final de su función.

Básicamente, assertsolo está ahí para evitar que suceda algo realmente malo, pero no debería ser posible que la condición se evalúe como falsa.

Nota histórica:

Las funciones require()y assert()se agregaron a Solidity antes de la bifurcación Byzantium, en v0.4.10. Antes de Byzantium, se comportaban de manera idéntica, pero ya compilados en diferentes códigos de operación. Esto significó que algunos contratos implementados antes de Byzantium se comportaron de manera diferente después de la bifurcación, siendo la principal diferencia que comenzaron a reembolsar el gas no utilizado.

Pero ambos assert()y require()el cambio de reversión se escriben en la cadena de bloques para que balance[_to] =balance[_from] +_valuese reviertan si assert()se require()activa una condición, ¿no es así? (Estoy hablando después del hardfork del año pasado). ¿O se assert()sigue haciendo el cambio?
Ambos cambios de reversión, lo cual se indica en la sección de Eficiencia de Gas.
Realmente ya no entiendo el punto de usar afirmar ya que tengo exactamente el mismo comportamiento con require pero tengo la posibilidad de reembolsar algo de gasolina, ¿cuál es el propósito?
La diferencia también es semántica, es decir. "Esta condición ni siquiera debería ser alcanzable. Intente compilar este código con una versión reciente de solc: gist.github.com/maurelian/02904ae729fb11213cde20ba05a202e6 Le advertirá que es posible que la segunda afirmación sea verdadera para ciertos valores.

Estoy usando requirepara la validación de entrada, ya que es un poco más eficiente que if/throw.

function foo(uint amount) {
    require(amount < totalAmount);
    ...
}

Donde as assertdebería usarse más para la captura de errores en tiempo de ejecución:

function foo(uint amount) {
    ...
    __check = myAmount;
        myAmount -= amount;
    assert(myAmount < __check);
    ...
}

revertrevertirá los cambios y reembolsará el gas no utilizado en una versión posterior de Ethereum, pero ATM actúa de la misma manera que throw.

¿Podría también simplemente escribir assert(myAmount-amount<myAmount) y omitir la variable __check? ¿Cuál es mejor, por qué/por qué no?
Es mucho más seguro no tener una duplicación innecesaria de lógica en la mutación del estado y el assertargumento. Hacerlo podría conducir a una pesadilla de depuración en la que las operaciones se encadenaban para el estado pero se olvidaban para la aserción.
Pero ambos assert()y require()el cambio de reversión se escriben en la cadena de bloques para que balance[_to] =balance[_from] +_valuese reviertan si assert()se require()activa una condición, ¿no es así? (Estoy hablando después del hardfork del año pasado). ¿O se assert()sigue haciendo el cambio? Pero ambos assert()y require()el cambio de reversión se escriben en la cadena de bloques para que balance[_to] =balance[_from] +_valuese reviertan si assert()se require()activa una condición, ¿no es así? (Estoy hablando después del hardfork del año pasado). ¿O se assert()sigue haciendo el cambio?

Creo que ninguna de las respuestas es correcta.

assertestá reservado para condiciones con las que se espera que las herramientas de análisis de código estático (tal vez el compilador de Solidity en versiones futuras) puedan detectar el error que advierte al desarrollador en el momento de la compilación.

requireestá reservado para condiciones de error de datos de entrada incorrectos a funciones (en comparación con datos de entrada esperados/válidos) que no se pueden detectar hasta el momento de la ejecución. Esto corresponde a las condiciones previas de la función en el lenguaje de programación argot. El compilador no puede ayudar debido a las infinitas posibilidades de los datos de entrada.

throwestá en desuso a favor de revertir.

revertestá reservado para condiciones de error que afectan la lógica empresarial. Por ejemplo, alguien envía un voto cuando la votación ya está cerrada.

requirey revertson en su mayoría similares con respecto a la implementación interna de EVM, pero los desarrolladores apreciarán la distinción.

Respuesta muy actualizada. +1

Assertes adecuado para verificar condiciones que no deberían ocurrir pero ocurren.

Requirees adecuado para verificar las condiciones no deseadas que pueden ocurrir.

Solidity tiene un SMTChecker que hace que su uso sea assertmuy bueno porque puede probar que sus invariantes son verdaderas:

Solidity implementa un enfoque de verificación formal basado en SMT (teorías del módulo de satisfacción) y resolución de Horn . El módulo SMTChecker intenta probar automáticamente que el código cumple con las especificaciones dadas por requirey assertdeclaraciones. Es decir, considera requirelas declaraciones como suposiciones e intenta demostrar que las condiciones dentro de las assertdeclaraciones son siempre verdaderas. Si se encuentra una falla en la aserción, se le puede dar un contraejemplo al usuario que muestre cómo se puede violar la aserción. Si el SMTChecker no da ninguna advertencia para una propiedad, significa que la propiedad es segura.

Debe usar assertpara ayudarlo a encontrar invariantes que han sido violados.

Usas assertpara ayudarte a atrapar cuando sucede lo imposible.

El tutorial de SMTChecker proporciona un buen ejemplo de assertque es demasiado largo para incluirlo aquí: recomiendo leerlo.

Si el SMTChecker lo emociona para comenzar a usar assert, eso es algo muy bueno. Pero recuerde que son para invariantes , como mencionan los documentos de Solidity :

Assert solo debe usarse para probar errores internos y verificar invariantes. El código que funciona correctamente nunca debe crear un pánico, ni siquiera en una entrada externa no válida. Si esto sucede, entonces hay un error en su contrato que debe corregir. Las herramientas de análisis de lenguaje pueden evaluar su contrato para identificar las condiciones y llamadas de función que causarán un pánico.