Revert()/throw/invalid son tan poderosos que incluso pueden revertir un contrato ya autodestruido, pero ¿hay alguna forma de superar esa limitación?

Sé que esto suena confuso, así que déjame explicarte:

En este caso, una transacción tendrá éxito y un contrato se destruirá primero y luego se reiniciará en la misma dirección (sí, no pensé que fuera posible... pero aparentemente lo es):

contract A
{
  address contractb = // some address here 
  function first() public
  {
    contractb.call.value(1 ether)();
  }
}

contract B
{
  address contractc = //all addresses should be known beforehand btw.
  function () public payable
  {
    if(contractc.value(msg.value)())
    {
      assembly { invalid } // in fact just "throw;" should do the trick...
    }
  }
}

contract C
{
  function () public payable
  {
    selfdestruct(msg.sender);
  }
}

Lo que pensé que sucedería:

  1. A llama a B, enviando 1 éter
  2. B reenvía este 1 éter a C a través de otra llamada
  3. Al recibir de B, C se autodestruye y envía todo el éter que tiene a B
  4. B devuelve la llamada a A y la transacción de "reclamaciones" falló.

Lo que realmente sucedió:

  1. A llamado B
  2. B reenviado a C
  3. Ahora B envía a C que se autodestruye... sin embargo, B vuelve a crear el contrato en la misma dirección con el mismo historial de transacciones excepto... no se envió nada, por lo que "no válido" en el ensamblaje parece lo mismo que revertir (). Pensé que la autodestrucción no se puede deshacer, pero aquí B tenía que verificar si la transferencia tuvo éxito y, si lo hace, envía todos los pasos al principio, por lo que B incluso cambió el contrato destruido (C) para reiniciarlo (estoy recibiendo esto tanto de etherscan como de remix fuera de la cadena; sí, antes de que se escriba un bloque... todo es una transacción que tiene varias transacciones internas).

Volviendo a mi pregunta original: ¿es posible evitar la reversión completa ya que incluso la autodestrucción no puede? ¿Qué sucede si B implementa un nuevo contrato desde dentro del código? ¡Gracias!

Respuestas (2)

Si algún código revierte/lanza, la llamada de contrato actual no tendrá efectos, pero no necesariamente la transacción completa. La instrucción de ensamblaje CALLdel código que se revirtió devolverá falso para indicar que se revirtió.

El lenguaje de solidez oculta esto revirtiendo todo por defecto si alguno CALLfalla.

Por ejemplo, esto:

someone.transfer(1 ether);

En realidad solo compila esto:

(bool success, ) = contractb.call.value(1 ether).gas(2100)("");
require(success);

Si usa manualmente un nivel bajo, .callno necesita revertir cuando successes falso, también puede ignorarlo o hacer algo diferente. De esa manera, puede evitar que se revierta toda la transacción cuando solo se revirtió una llamada.

"Revertir" no es "retroceder". No deshace las acciones realizadas por transacción una por una. En su lugar, finaliza las transacciones sin guardar los cambios realizados en la cadena de bloques. Por lo tanto, funciona como la función "revertir" en el editor de texto, no como la función "deshacer".

En cualquier caso, veo que me sorprendió que incluso la autodestrucción no hiciera el 'truco' ... en cualquier caso, parece que solo detener (devolver 0) y revertir/lanzar = deshacer completo son las únicas formas de manejarlo. O bien: como lo llamaste "Revertir" != deshacer pero aún mantengo mi curiosidad de cómo se puede lograr algo como lo anterior.
La transacción que realmente tuvo éxito (dejó cualquier cambio en la cadena de bloques) no puede afirmar que falló. Muchos contratos inteligentes se basan en el hecho de que se garantiza que la transacción fallida no deja rastro en la cadena de bloques (sin cambios de saldo, sin cambios de almacenamiento, sin eventos registrados, sin contratos inteligentes nuevos o destruidos). Romper esta regla abriría un vector de ataque a dichos contratos.
cierto, aunque me las arreglé para hacer un "Error" (transacción bien exitosa...) que resultó ser con malas instrucciones en general... ¿el truco? Toda la transacción fue una combinación de fallas/éxitos, lo que significa que la parte importante (también conocida como tomar éter...) tuvo éxito, pero la última no causó "error". En última instancia, este NO es un problema de seguridad a partir de ahora. No puedo presentar un caso en el que se tome dinero pero el resultado sea falso para cada subconjunto... que será el principal problema de seguridad del que está hablando.