es una colección de desafíos que lo ayudarán a comprender mejor la máquina virtual Ethereum. Cada rompecabezas comienza brindándole una serie de códigos de operación y le pide que ingrese el valor de transacción correcto o los datos de llamada que permitirán que la secuencia se ejecute sin revertir. Este recorrido tiene como objetivo ser una guía de bajo impacto para cada rompecabezas, lo que facilita que cualquier persona con cualquier nivel de experiencia comprenda completamente el por qué y el cómo detrás de cada solución. Este tutorial asumirá que está familiarizado con las máquinas apiladoras. Si no es así, eche un vistazo a antes de comenzar. Es útil saber que cada elemento en la pila en el EVM tiene 32 byes (es decir, una palabra). En este repositorio, hay 10 rompecabezas. Para alguien sin experiencia con EVM, esto debería tomar alrededor de 1 a 2 horas. Para alguien con experiencia básica en EVM, esto debería tomar alrededor de 1 hora. Si se siente muy cómodo con el EVM pero aún desea realizar el tutorial, esto debería tomar alrededor de 30 minutos. Con esa nota, ¡estamos listos para comenzar! EVM-Puzzles cómo funcionan las máquinas apiladoras Primero, diríjase al , clone el proyecto y configure su entorno local. Asegúrese de tener instalado el casco. Si no lo hace, simplemente ingrese cuando esté en la carpeta del proyecto raíz. repositorio de EVM-Puzzles npm install --save-dev hardhat A continuación, si es nuevo en EVM, eche un vistazo breve a los códigos de operación de (no sienta la necesidad de entender todo, solo tenga una idea general). EVM Con todo eso fuera del camino, echemos un vistazo al primer rompecabezas. Para iniciar el primer rompecabezas, ingrese al directorio raíz del proyecto e ingrese en la terminal. npx hardhat play Rompecabezas #1 Echemos un vistazo al primer rompecabezas. Se le da una serie de códigos de operación que representan un contrato. El acertijo le solicita que ingrese un valor para enviar, o en otras palabras, si envió una transacción a este contrato, ¿cuál debería ser el valor de la transacción para que este contrato se ejecute sin presionar la ? Adelante, pruébalo y luego siéntete libre de volver aquí si te quedas atascado o si quieres ver en profundidad la solución después de resolver el rompecabezas. instrucción REVERT ############ # Puzzle 1 # ############ 00 34 CALLVALUE 01 56 JUMP 02 FD REVERT 03 FD REVERT 04 FD REVERT 05 FD REVERT 06 FD REVERT 07 FD REVERT 08 5B JUMPDEST 09 00 STOP ? Enter the value to send: (0) Bien, ahora para la explicación. Primero, necesitamos saber qué hace la . Este código de operación obtiene el valor de la llamada actual (es decir, el valor de la transacción) en wei y empuja ese valor a la parte superior de la pila. Entonces, si ingresamos un valor de 10, antes de que la instrucción sea instrucción CALLVALUE CALLVALUE evaluado, la pila se vería así. [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Después de evaluar el código de operación , la pila se vería así. CALLVALUE [10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación, necesitamos saber qué hace la instrucción . Este código de operación consume el valor superior en la pila y salta a la -ésima instrucción en la secuencia donde es el valor en la parte superior de la pila. Un ejemplo rápido hará esto más claro. Digamos que tenemos la siguiente secuencia. JUMP n n 00 34 CALLVALUE 01 56 JUMP 02 FD REVERT 03 FD REVERT 04 80 JUMPDEST 05 80 DUP1 06 00 STOP ? Enter the value to send: (0) Si ingresamos 4 como el valor a enviar, el código de operación empujará a la pila. Después de , ahora nuestra pila se ve así. CALLVALUE 4 CALLVALUE [4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Luego, el código de operación consume el valor superior en la pila y salta a la instrucción en esa posición. Dado que el valor en la parte superior de la pila es , el contador del programa salta a la cuarta instrucción y continúa. Un código de operación debe alterar el contador del programa para terminar en una instrucción . Para el ejemplo anterior, podemos pensar en el programa con este aspecto después de evaluar la instrucción . JUMP 4 JUMP JUMPDEST JUMP 05 80 DUP1 06 00 STOP Ahora que todo eso está claro, volvamos al rompecabezas. Necesitamos ingresar un valor para que el programa se ejecute sin presionar una instrucción . REVERT 00 34 CALLVALUE 01 56 JUMP 02 FD REVERT 03 FD REVERT 04 FD REVERT 05 FD REVERT 06 FD REVERT 07 FD REVERT 08 5B JUMPDEST 09 00 STOP Para hacer esto, podemos ingresar un valor de llamada de 8, lo que hace que la instrucción empuje a la pila donde la instrucción luego consume ese valor y salta a la octava instrucción, omitiendo todas las instrucciones . ¡Buen trabajo, un rompecabezas menos! CALLVALUE 8 JUMP REVERT Rompecabezas #2 Ahora que tienes los pies mojados, echemos un vistazo al segundo rompecabezas. Pruébelo usted mismo y, al igual que antes, siéntase libre de volver para ver la solución y la explicación. Aquí está el rompecabezas. ############ # Puzzle 2 # ############ 00 34 CALLVALUE 01 38 CODESIZE 02 03 SUB 03 56 JUMP 04 FD REVERT 05 FD REVERT 06 5B JUMPDEST 07 00 STOP 08 FD REVERT 09 FD REVERT ? Enter the value to send: (0) Al igual que antes, debemos ingresar un valor de transacción para enviar que hará que el programa se ejecute sin revertir. Si echamos un vistazo a la secuencia de instrucciones, podemos ver que necesitamos el código de operación para alterar el contador del programa a la sexta instrucción. Al igual que antes, la primera instrucción es , por lo que sabemos que el valor que ingresamos terminará en la parte superior de la pila después de la primera instrucción. JUMP CALLVALUE Echemos un vistazo a la . Este código de operación obtiene el tamaño del código que se ejecuta en el entorno actual. En este ejemplo, podemos verificar manualmente el tamaño del código observando cuántos códigos de operación hay en la secuencia. Cada código de operación es de 1 byte, y en este rompecabezas tenemos 10 códigos de operación, lo que significa que el tamaño del código es de 10 bytes. Como nota al margen importante, el EVM usa números hexadecimales para representar el código de bytes. Si no está familiarizado, vea . Con esto en mente, podemos saber que se coloca en la pila, lo que representa 10 bytes. instrucción CODESIZE cómo funcionan los números hexadecimales 0a El siguiente código de operación con el que nos encontramos es la , que toma el primer elemento de la pila menos el segundo elemento de la pila, colocando el resultado en la parte superior de la pila. Se consumen ambas entradas en la parte superior de la pila antes de la instrucción . Por ejemplo, si tuviéramos una pila que se viera así. instrucción SUB SUB [3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Ejecutar la instrucción produciría el siguiente resultado de pila. SUB [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Con esta información volvamos al rompecabezas. Ahora sabemos que el programa primero evalúa la instrucción , colocando el valor que ingresamos en la pila. Luego, el programa evalúa la instrucción , que inserta (que representa 10 bytes) en la pila. También sabemos que necesitamos para cambiar el contador del programa a la sexta instrucción. Así es como se ve la pila después de la instrucción . CALLVALUE CODESIZE 0a JUMP CODESIZE [a your_input 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Si aún no ha terminado el rompecabezas, continúe e intente usar la información anterior para ingresar el valor correcto. De lo contrario, siéntase libre de seguir leyendo para conocer el último paso de la solución. Como sabemos que la instrucción es la siguiente, debemos ingresar un valor tal que igual a , lo que hace que nuestra respuesta sea 4. SUB 0a - your_input 6 Rompecabezas #3 Prepárate para cambiar de marcha un poco. En lugar de ingresar un valor de transacción para resolver el rompecabezas, tendremos que ingresar los datos de la llamada. Calldata es un espacio direccionable por bytes de solo lectura donde se guardan los datos de transacción durante un mensaje o una llamada. En lenguaje sencillo, esta es la carga útil del código de bytes que se adjunta a un mensaje ( ). Echemos un vistazo al rompecabezas. haga clic aquí para obtener más información sobre la anatomía de una transacción en Ethereum ############ # Puzzle 3 # ############ 00 36 CALLDATASIZE 01 56 JUMP 02 FD REVERT 03 FD REVERT 04 5B JUMPDEST 05 00 STOP ? Enter the calldata: Para este rompecabezas, es útil saber que 1 byte son 8 bits y que los números del 0 al 255 pueden representar un byte en el EVM. Este rompecabezas también nos presenta un nuevo código de operación llamado . Esta instrucción obtiene el tamaño de los datos de la llamada en bytes y los coloca en la pila. CALLDATASIZE Con ese conocimiento, esto hace que el rompecabezas sea bastante sencillo. Tendremos que pasar datos de llamada de modo que la instrucción empuje 4 en la pila. A partir de ahí, la instrucción saltará a la cuarta instrucción de la secuencia, llegando a . Para mantenerlo simple, se puede usar para representar 1 byte ya que en hexadecimal se evalúa como 255 en formato decimal. Todo lo que tenemos que hacer es copiar cuatro veces, haciendo que el código de bytes que debemos ingresar sea: . ¡Otro rompecabezas caído! CALLDATASIZE JUMP JUMPDEST 0xff ff ff 0xffffffff Rompecabezas #4 Ingrese bit a bit. En este rompecabezas vemos nuestra primera instrucción . Como de costumbre, siéntete libre de intentarlo y descubrirlo por tu cuenta. Cuando esté listo, regrese aquí para encontrar la solución y la explicación. XOR ############ # Puzzle 4 # ############ 00 34 CALLVALUE 01 38 CODESIZE 02 18 XOR 03 56 JUMP 04 FD REVERT 05 FD REVERT 06 FD REVERT 07 FD REVERT 08 FD REVERT 09 FD REVERT 0A 5B JUMPDEST 0B 00 STOP ? Enter the value to send: (0) Sabemos que empujará el valor que ingresamos a la parte superior de la pila. También podemos saber qué tan grande es el observando cuántas instrucciones hay. En este programa, tenemos 12 instrucciones, lo que hace 12 bytes o en hexadecimal, que se envía a la pila. Así que ahora nuestra pila se ve así. CALLVALUE CODESIZE 0c [c your_input 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Echemos un vistazo a la . Esta instrucción evalúa dos números en su representación binaria y devuelve un en cada posición de bit donde los bits de son s. Echemos un vistazo a un ejemplo rápido. Digamos que tenemos dos números en la parte superior de la pila. instrucción XOR 1 cualquiera de los operandos, pero no de ambos, 1 [5 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Al ejecutar la instrucción , podemos imaginar los dos números en representación binaria así. XOR 00000000000000000000000000000101 00000000000000000000000000000011 Luego, poco a poco, los dos números se evalúan uno contra el otro. Si un bit es un y el otro bit es un , el bit resultante será un , si ambos bits son s o ambos bits son s, el número resultante es un . Así que el resultado de es este. 0 1 1 0 1 0 5 XOR 3 00000000000000000000000000000110 De vuelta al rompecabezas. Sabemos que tenemos en la parte superior de la pila y en la segunda posición de la pila. Después del , el código de operación debe enviarnos a la décima instrucción. Ahora, con toda esta información conocida, solo necesitamos ingresar un valor de llamada para que el en hexadecimal. Adelante, pruébalo por tu cuenta. 0c your_input XOR JUMP 0c XOR callvalue 10 Bien, ahora para los pasos finales. Sabemos que necesitamos que el resultado de sea , que en binario se representa como . También tenemos en la pila, que en binario se representa como . Así que ahora necesitamos encontrar un número tal que resulte en , lo que hace que el número que necesitamos ingresar sea . Esto se evalúa como el número hexadecimal . ¡6 es nuestra respuesta! XOR 10 1010 0c 1100 c XOR your_input 1010 0110 06 Rompecabezas #5 Bienvenido al siguiente rompecabezas, donde nos encontramos con algunos códigos de operación nuevos. Siéntete libre de darle una oportunidad. Mientras tanto, echemos un vistazo a la secuencia de instrucciones de este rompecabezas. ############ # Puzzle 5 # ############ 00 34 CALLVALUE 01 80 DUP1 02 02 MUL 03 610100 PUSH2 0100 06 14 EQ 07 600C PUSH1 0C 09 57 JUMPI 0A FD REVERT 0B FD REVERT 0C 5B JUMPDEST 0D 00 STOP 0E FD REVERT 0F FD REVERT ? Enter the value to send: (0) se encuentra con el lector, el lector se encuentra con . La es bastante sencilla. Duplica el valor en la primera posición de la pila y empuja el duplicado a la parte superior de la pila. De manera similar, duplicaría el valor en la segunda posición de la pila y empujaría el valor duplicado hacia la parte superior. Hay instrucciones DUP para todas las posiciones en la pila ( ). DUP1 DUP1 instrucción DUP1 DUP2 DUP1-DUP16 Echando un vistazo a las dos primeras instrucciones del rompecabezas, primero se ejecuta , empujando el valor que ingresamos a la parte superior de la pila. Luego se ejecuta , duplicando el valor que ingresamos y empujándolo a la parte superior de la pila. Ahora, después de las dos primeras instrucciones, nuestra pila se ve así. CALLVALUE DUP1 [your_input your_input 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Entonces nos encontramos con otra nueva instrucción. La toma los dos primeros valores de la pila, los multiplica y coloca el resultado en la parte superior de la pila. Entonces, en este caso, se multiplica por y la pila resultante se ve así. instrucción MUL your_input your_input [mul_result 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación nos encontramos con la . Esta instrucción empuja un valor de 2 bytes en la parte superior de la pila. Cuando vea cualquier instrucción , siempre estará acompañada por el valor que empujará. Por ejemplo, en nuestro rompecabezas tenemos , lo que significa que empujará el número hexadecimal de 2 bytes en la parte superior de la pila. Hay instrucciones push de a . instrucción PUSH2 PUSH PUSH2 0100 0100 PUSH1 PUSH32 Volviendo a nuestro rompecabezas, dado que la siguiente instrucción es , nuestra pila resultante ahora se verá así. PUSH2 0100 [0100 mul_result 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Ahora nos encontramos con la . Esta instrucción toma los dos primeros valores de la pila, ejecuta una comparación de igualdad y coloca el resultado en la parte superior de la pila. Si los dos primeros valores son iguales, se coloca en la parte superior; de lo contrario, se coloca en la pila. Ambos valores en las posiciones 1 y 2 de la pila se consumen de la instrucción . instrucción EQ 1 0 EQ Para simplificar, digamos que es , de modo que cuando se evalúa la instrucción , se empuja a la pila, haciendo que nuestra pila ahora se vea así. mul_result 0100 EQ 1 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] La siguiente instrucción que se evalúa es que empuja a la parte superior de la pila. Siguiendo esta instrucción, vemos otra nueva instrucción. La alterará condicionalmente el contador del programa. Esta instrucción mira el segundo elemento de la pila para saber si debe saltar o no, dependiendo de si el segundo elemento de la pila es un o un . Luego, el primer elemento de la pila se usa para saber a qué posición saltar. La instrucción consume ambos valores en la parte superior de la pila durante este proceso. Así que echando un vistazo a nuestro rompecabezas, después de la instrucción , nuestra pila se ve así. PUSH1 0C 0c instrucción JUMPI 1 0 JUMPI PUSH1 0c [0c 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Primero, la instrucción verifica el segundo elemento de la pila. En este caso es indicando que el programa debe saltar. Luego, verifica el primer elemento de la pila para saber a dónde debe saltar. El valor de la pila superior es , lo que significa que saltará a la instrucción número 12, que es nuestro . JUMPI 1 JUMPI 0c JUMPDEST ¡Y eso completará nuestro rompecabezas! Entonces, con toda esta información, ahora sabemos que debemos ingresar un valor de llamada para que cuando se duplique una vez (haciendo que los dos primeros elementos en la pila sean el valor de llamada), y después de que se multipliquen los valores de la pila superior, nuestro resultado sea el número hexadecimal . Siéntase libre de intentarlo desde aquí y ver si puede resolverlo. 0100 Ok ahora para los pasos finales. Podemos convertir en un número decimal y obtener 256. Luego podemos sacar la raíz cuadrada de 256 ya que esencialmente multiplica el número por sí mismo. ¡El número resultante es 16, que es la respuesta a este acertijo! 0100 DUP1 MUL Rompecabezas #6 ¡5 acertijos abajo, 5 para terminar! Como de costumbre, pruebe el rompecabezas y luego vuelva aquí para encontrar la solución y la explicación. ############ # Puzzle 6 # ############ 00 6000 PUSH1 00 02 35 CALLDATALOAD 03 56 JUMP 04 FD REVERT 05 FD REVERT 06 FD REVERT 07 FD REVERT 08 FD REVERT 09 FD REVERT 0A 5B JUMPDEST 0B 00 STOP ? Enter the calldata: Saluda a la . Esta instrucción obtiene los datos de entrada de los datos de llamada adjuntos a una transacción. Hay algunas cosas importantes a tener en cuenta sobre este código de operación. espera un número entero en la parte superior de la pila para saber desde qué byte empezar a cargar los datos de la llamada. Por ejemplo, si envía una transacción con una secuencia de 32 bytes como datos de llamada y coloca en la parte superior de la pila, cuando ejecute , todos los datos de llamada del byte 8 al byte 32 se colocarán en la parte superior de la pila. Como nota adicional, si los datos de llamada son 64 bytes y necesita acceder a los segundos 32 byes de la secuencia, puede insertar en la pila y luego usar para obtener los segundos 32 byes de la secuencia. instrucción CALLDATALOAD CALLDATALOAD 08 CALLDATALOAD 20 CALLDATALOAD Ahora volvamos al rompecabezas. Podemos ver que hay seguido de , lo que significa que los datos de la llamada se cargarán a partir del byte 0 y los bytes 0-32 de los datos de la llamada se colocarán en la parte superior de la pila. Podemos ver que la instrucción necesita alterar el contador del programa a (es decir, la décima instrucción). Siéntase libre de detenerse aquí e intentar resolver el resto del rompecabezas. PUSH1 00 CALLDATALOAD JUMP 0a Bien, repasemos los pasos finales. Sabemos que los datos de llamada están en hexadecimal, por lo que puede parecer intuitivo ingresar como datos de llamada para completar el rompecabezas, pero es posible que haya notado que esto no funciona. Esto se debe a que cuando se envía calldata, dado que la secuencia de bytes no era de 32 bytes, se rellena a la derecha, por lo que lo que pensamos que era , en realidad se convierte en . Entonces, lo que debemos hacer es rellenar nuestro con 31 bytes a la izquierda, convirtiéndolo en . Ahí lo tienes, ¡esa es nuestra respuesta! 0x0a 0a a00000000000000000000000000000000000000000000000000000000000000 0x0a 0x000000000000000000000000000000000000000000000000000000000000000a Rompecabezas #7 Ya sabes que hacer. Pruebe el rompecabezas y luego regrese para ver la solución / explicación completa. ############ # Puzzle 7 # ############ 00 36 CALLDATASIZE 01 6000 PUSH1 00 03 80 DUP1 04 37 CALLDATACOPY 05 36 CALLDATASIZE 06 6000 PUSH1 00 08 6000 PUSH1 00 0A F0 CREATE 0B 3B EXTCODESIZE 0C 6001 PUSH1 01 0E 14 EQ 0F 6013 PUSH1 13 11 57 JUMPI 12 FD REVERT 13 5B JUMPDEST 14 00 STOP ? Enter the calldata: Lo primero es lo primero, podemos ver y saber que necesitaremos ingresar calldata con un tamaño específico para resolver este rompecabezas. Tomemos nota de esto y volvamos más tarde. Después de que el tamaño de los datos de la llamada se inserta en la pila, hay y , lo que hace que nuestra pila en este punto se vea así. CALLDATASIZE PUSH1 00 DUP1 [0 0 calldata_size 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación nos encontramos con la . Esta instrucción copia los datos de entrada de la transacción y los guarda en la memoria. Este código de operación espera tres elementos en la parte superior de la pila que son , en este orden. es el desplazamiento de bytes en la memoria donde se copiará el resultado. No hemos hablado mucho sobre la memoria en este momento y si desea obtener más información, puede . La versión abreviada es que hay una estructura de datos temporal que asigna espacio para contener valores durante la ejecución de una función y le dice al programa en qué ranura de la memoria almacenar los datos que se copian de calldata. El dicta desde dónde comenzar a copiar los datos de la llamada (tal como lo hace en el último ejemplo) y el le dice al programa cuánto de la secuencia de bytes almacenar en la memoria. Durante este proceso, se consumen los tres elementos superiores de la pila. instrucción CALLDATACOPY [destOffset offset size] destOffset leer al respecto aquí destOffset offset CALLDATALOAD size Con todo esto conocido, revisemos nuestra pila actual. [0 0 calldata_size 0 0 0 0 0 0 0 0 0 0 0 0 0] Cuando se ejecuta la instrucción , almacenará los datos de llamada en la ranura de memoria , comenzando en el byte y almacenando el tamaño de todos los datos de llamada. Nuestra pila resultante después de esta instrucción se ve así. CALLDATALOAD 0 0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Inmediatamente después, , haciendo que la pila se vea como la teníamos antes . CALLDATASIZE PUSH1 00 PUSH1 00 CALLDATALOAD [0 0 calldata_size 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación, se nos presenta otro código de operación nuevo, la . Esta instrucción crea una nueva cuenta (es decir, contrato o EOA). Analicemos un poco el capó con el código de operación , ya que esto será útil más adelante durante el tutorial (y generalmente es bueno saberlo). instrucción CREAR CREATE Al implementar un nuevo contrato con el código de operación , la pila debe tener en la parte superior de la pila, en este orden. El es la cantidad de wei para enviar el nuevo contrato que se está creando, el es la ubicación en la memoria donde comienza el código de bytes que se ejecutará en la implementación y el es el tamaño del código de bytes que se ejecutará en la implementación. Cuando implementa un contrato con el código de operación , el código de bytes del no es el código de bytes del nuevo contrato, sino que el código de bytes del se ejecuta durante la implementación y el se convierte en el código de bytes del contrato recién creado. CREATE [value offset size] value offset size CREATE offset offset valor devuelto Veamos un ejemplo rápido que hará que esto sea fácil de entender. Si usa el código de operación con el código de bytes de implementación de , dado que el valor de retorno de esta secuencia de código de bytes es , el código de bytes del contrato recién creado será , es decir. . Entonces, cuando llame a este contrato, ¡simplemente ejecutará ! Asegúrese de tomar nota de este concepto, ya que será útil más adelante. CREATE 0x6160016000526002601Ef3 6001 6001 PUSH1 01 PUSH1 01 Cuando se ejecuta la instrucción , se consumen los tres valores y la dirección en la que se implementó la cuenta se coloca en la parte superior de la pila. Después de que se ejecuta este código de operación, nuestra pila se ve así. CREATE [address_deployed 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación, nos encontramos con la que espera una dirección en la parte superior de la pila y devuelve el tamaño del código en esa dirección. La dirección en la parte superior de la pila se consume en este proceso. Después vemos haciendo que nuestra pila se vea así. instrucción EXTCODESIZE EXTCODESIZE PUSH1 01 [01 address_code_size 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Inmediatamente después, se ejecuta la instrucción , comprobando si los dos valores superiores son iguales y colocando el resultado en la pila. A partir de ahí, y se utilizan para llevarnos al . Entonces, volviendo al comienzo del rompecabezas, ¡tendremos que ingresar los datos de llamada de manera que el tamaño del código sea igual a 01 byte! Esto es un poco complicado, así que para entenderlo, podemos ver el ejemplo del patio de recreo de la . Así es como se ve el ejemplo. EQ PUSH1 13 JUMPI JUMPDEST instrucción EXTCODESIZE // Creates a constructor that creates a contract with 32 FF as code PUSH32 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0 MSTORE //Opcodes to return 32 ff PUSH32 0xFF60005260206000F30000000000000000000000000000000000000000000000 PUSH1 32 MSTORE // Create the contract with the constructor code above PUSH1 41 PUSH1 0 PUSH1 0 CREATE // Puts the new contract address on the stack // The address is on the stack, we can query the size EXTCODESIZE Echemos un vistazo más de cerca a los códigos de operación en el constructor. // Push a 32 byte value onto the stack PUSH32 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 00 // Store the 32 byte value at memory slot 0 MSTORE // Return a 32 byte value starting at memory slot 0 PUSH1 20 PUSH1 00 RETURN STOP STOP ... Cuando se ejecuta este código, devuelve un valor de que es de 32 bytes. Si cambiamos el tamaño de retorno a 16 bytes en lugar de 32 bytes, será , que es 16 bytes en hexadecimal. Esto indica que usa el tamaño del valor devuelto para dictar el tamaño del código. ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff EXTCODESIZE 10 EXTCODESIZE Vamos a terminar el rompecabezas. Ahora sabemos que evalúa el tamaño del valor de retorno del código de bytes implementado. Con esta información, podemos pasar datos de llamada de manera que cuando se implemente, ¡devuelva un valor de 1 byte! Puede usar cualquier secuencia de códigos de operación que devuelva 1 byte, pero para este tutorial, usaremos . Y con eso, ¡otro rompecabezas resuelto! EXTCODESIZE 0x60016000526001601ff3 Rompecabezas #8 Bienvenido al octavo rompecabezas. Echemos un vistazo a lo que está en la tienda. ############ # Puzzle 8 # ############ 00 36 CALLDATASIZE 01 6000 PUSH1 00 03 80 DUP1 04 37 CALLDATACOPY 05 36 CALLDATASIZE 06 6000 PUSH1 00 08 6000 PUSH1 00 0A F0 CREATE 0B 6000 PUSH1 00 0D 80 DUP1 0E 80 DUP1 0F 80 DUP1 10 80 DUP1 11 94 SWAP5 12 5A GAS 13 F1 CALL 14 6000 PUSH1 00 16 14 EQ 17 601B PUSH1 1B 19 57 JUMPI 1A FD REVERT 1B 5B JUMPDEST 1C 00 STOP ? Enter the calldata: Este puede parecer más desalentador, pero en realidad es bastante simple. Primero vemos un muy similar que, al igual que el rompecabezas anterior, crea un nuevo contrato a partir de los datos de llamada que pasa y devuelve la dirección de implementación. Entonces, desde el principio, sabemos que tendremos que ingresar datos de llamada con código de bytes para un contrato para resolver el rompecabezas. Tomemos una nota mental rápida de cómo se ve la pila en este punto. Dado que la instrucción consume los tres valores principales de la pila y envía la dirección en la que se implementó la cuenta, nuestra pila ahora se ve así. CALLDATASIZE PUSH1 00 DUP1 CALLDATACOPY CALLDATASIZE PUSH1 00 PUSH1 00 CREATE CREATE [address_deployed 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Las siguientes 5 instrucciones se relacionan con la . Esta instrucción crea un nuevo subcontexto y ejecuta el código de la cuenta dada, luego reanuda la cuenta actual. En lenguaje sencillo, la instrucción se usa para interactuar con otro contrato. Este código de operación espera que la pila tenga algunos valores en la parte superior de la pila , en este orden. Veamos cada uno de los argumentos uno por uno. es la cantidad de gas que se enviará con el mensaje de llamada. es la dirección a la que se enviará el mensaje. es la cantidad de wei que se enviará con el mensaje. es la ubicación en la memoria dentro del contexto actual (es decir, el msg.sender) que se usará como datos de llamada para la llamada del mensaje. es el tamaño de los datos de la llamada que se enviarán con la llamada del mensaje. es la ubicación en la memoria dentro del contexto actual donde se almacenará el valor de retorno de la llamada. Finalmente, es el tamaño del valor de retorno que se almacenará en la memoria. instrucción CALL CALL [gas address value argsOffset argsSize retOffset retSize] gas address value argsOffset argsSize retOffset retSize Ahora echemos un vistazo al rompecabezas de nuevo. Los siguientes cuatro códigos de operación son , lo que hace que la pila se vea así. PUSH1 00 DUP1 DUP1 DUP1 DUP1 [0 0 0 0 0 address_deployed 0 0 0 0 0 0 0 0 0 0] Luego vemos la . Esta instrucción intercambia los elementos de la pila 1 y 6. Hay instrucciones para todas las posiciones en la pila ( - ). En este caso, intercambia con haciendo que nuestra pila ahora esté en el orden correcto para que coincida con . Así es como se ve nuestra pila ahora. instrucción SWAP5 SWAP SWAP1 SWAP16 SWAP5 0 address_deployed [gas address value argsOffset argsSize retOffset retSize] [address_deployed 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Luego ejecutamos la instrucción , que devuelve si el subcontexto se revirtió y si fue un éxito. Después de la instrucción , podemos ver un , lo que significa que necesitamos para insertar un en la pila. Continúe y pruebe el resto del rompecabezas, luego siéntase libre de volver para ver el resto de la solución. CALL 0 1 CALL PUSH1 00 EQ CALL 0 Bien, ahora sabemos que la instrucción debe devolver , lo que significa que debemos ingresar los datos de llamada que hacen que falle. Para que falle, hay tres formas. Una forma en que puede fallar es si no hay suficiente gasolina. La segunda forma en que puede fallar es si no hay suficientes valores en la pila. La tercera forma en que puede fallar es si el contexto de ejecución actual es de una y el valor en wei (índice de pila 2) no es 0 (desde la bifurcación de Byzantium). También es importante tener en cuenta que siempre tendrá éxito cuando a una cuenta sin código (o tamaño de código de 0). CALL 0 CALL CALL STATICCALL CALL CALL Para terminar este rompecabezas, volvamos a ver cómo funciona el código de operación . Sabemos que el valor de retorno del código de bytes que se ejecuta en la implementación se convierte en el código de bytes del contrato recién creado. Con esa información conocida, podemos pasar datos de llamada con una secuencia de código de bytes de modo que el valor de retorno de la secuencia provoque un cuando se ejecute. CREATE REVERT Puede pasar cualquiera que resulte en pero para el tutorial usaremos como código de bytes de implementación. Dado que el valor de retorno de esta secuencia de código de bytes es , el código del contrato recién creado será , es decir. la instrucción . Entonces, cuando llame a este contrato, ejecutará la instrucción , y dado que no hay valores en la pila en el subcontexto del contrato, ¡la fallará (es decir, )! Ahí lo tienes, es nuestra respuesta. REVERT 0x60016000526001601ff3 01 01 ADD ADD CALL REVERT 0x60016000526001601ff3 Rompecabezas #9 Estamos en la recta final, echemos un vistazo al rompecabezas #9. Este rompecabezas agrega una capa más de complejidad, lo que requiere que ingrese un valor de llamada y datos de llamada para resolver el rompecabezas. ############ # Puzzle 9 # ############ 00 36 CALLDATASIZE 01 6003 PUSH1 03 03 10 LT 04 6009 PUSH1 09 06 57 JUMPI 07 FD REVERT 08 FD REVERT 09 5B JUMPDEST 0A 34 CALLVALUE 0B 36 CALLDATASIZE 0C 02 MUL 0D 6008 PUSH1 08 0F 14 EQ 10 6014 PUSH1 14 12 57 JUMPI 13 FD REVERT 14 5B JUMPDEST 15 00 STOP ? Enter the value to send: (0) Ya estamos familiarizados con los dos primeros códigos de operación, por lo que podemos saber que después de las instrucciones , nuestra pila se ve así. CALLDATASIZE PUSH1 03 [03 calldata_size 0 0 0 0 0 0 0 0 0 0 0 0 0 0] La ejecuta una comparación de los dos primeros valores de la pila para ver si el primer elemento de la pila es menor que el segundo elemento de la pila. Si se evalúa como verdadero, se inserta en la pila; de lo contrario, se inserta en su lugar. Los dos valores utilizados en la comparación se consumen en este proceso. Por el bien del ejemplo, digamos que es de 4 bytes, por lo que empujará como resultado. instrucción LT LT 1 0 CALLDATASIZE LT 1 Dado que se evaluó como verdadero, el código salta a en la instrucción . Después del salto, y se colocan en la pila y los multiplica, consumiendo los dos valores superiores de la pila en el proceso. empuja a la pila y luego verifica si el resultado de es igual a , consumiendo los valores en el proceso. necesita empujar un a la pila para permitir que nos lleve al final del rompecabezas. LT JUMPDEST 09 CALLVALUE CALLDATASIZE MUL PUSH1 08 08 EQ MUL 08 EQ 1 JUMPI Con toda esta información, ahora sabemos que necesitamos ingresar datos de llamada de modo que sea mayor a 3 bytes, y el producto de sea . CALLDATASIZE CALLDATASIZE * CALLVALUE 08 Con un poco de matemática rápida, podemos usar cualquier combinación de valores que evalúen a 8 cuando se multiplican juntos que satisfagan las condiciones anteriores. Para el tutorial, ingresaremos como datos de llamada y como valor de llamada. ¡Un rompecabezas más para ir! 0x00000001 2 Rompecabezas #10 Aquí está, el rompecabezas final. Saltemos. ############# # Puzzle 10 # ############# 00 38 CODESIZE 01 34 CALLVALUE 02 90 SWAP1 03 11 GT 04 6008 PUSH1 08 06 57 JUMPI 07 FD REVERT 08 5B JUMPDEST 09 36 CALLDATASIZE 0A 610003 PUSH2 0003 0D 90 SWAP1 0E 06 MOD 0F 15 ISZERO 10 34 CALLVALUE 11 600A PUSH1 0A 13 01 ADD 14 57 JUMPI 15 FD REVERT 16 FD REVERT 17 FD REVERT 18 FD REVERT 19 5B JUMPDEST 1A 00 STOP ? Enter the value to send: (0) En este rompecabezas, deberá ingresar un valor de llamada y datos de llamada. Echemos un vistazo a las primeras instrucciones. Primero vemos que empuja el tamaño del código, seguido del valor de llamada que pasó y luego intercambia sus posiciones. En este punto, nuestra pila se ve así. CODESIZE CALLVALUE SWAP1 [1b callvalue 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación vemos la que opera exactamente como , pero evalúa mayor que en lugar de menor que. Para este acertijo, necesitamos que empuje en la pila, por lo que sabemos que nuestro valor de llamada debe ser menor que (es decir, 27 en notación decimal). Esto permitirá que el programa salte al primer en la instrucción . instrucción GT LT GT 1 1b JUMPDEST 08 Ahora vemos que empuja el tamaño de los datos de llamada y a la pila e intercambia sus posiciones. Ahora nuestra pila se ve así. CALLDATASIZE PUSH2 0003 SWAP1 0003 [calldata_size 3 0 0 0 0 0 0 0 0 0 0 0 0 0] A continuación vemos la . Esta instrucción ejecuta un módulo del primer elemento de la pila y el segundo elemento de la pila, empujando el resto a la pila. Siguiendo la instrucción vemos la , que empuja a la pila si el valor superior de la pila es . Si cualquier otro número está en la parte superior de la pila, se empuja a la pila en su lugar. En nuestro caso, necesitamos para empujar a la pila (volveremos a esto). Luego vemos . La simplemente suma los primeros dos valores en la pila y empuja el resultado a la pila. Siguiendo esta secuencia, hay un , lo que significa que necesita empujar la posición de a la pila. Siéntase libre de darle una oportunidad al resto del rompecabezas desde aquí. instrucción MOD MOD instrucción ISZERO 1 0 0 ISZERO 1 CALLVALUE PUSH1 0A ADD instrucción ADD JUMPI CALLVALUE PUSH1 0A ADD JUMPDEST Con toda esta información, ahora sabemos algunas cosas. Primero, debemos ingresar los datos de llamada de modo que el tamaño de los datos de llamada sea divisible por 3 bytes, lo que permite que la en la pila. Esto le permite a empujar un a la pila, donde el programa puede saltar al segundo . En segundo lugar, debemos ingresar un valor de llamada de modo que el valor sea menor que 26 y el valor de sea igual a . Con estos factores conocidos, podemos ingresar como datos de llamada y (en decimal) como valor de llamada. ¡Así de simple, hemos completado el rompecabezas final! CALLDATASIZE PUSH2 0003 SWAP1 MOD 0 ISZERO 1 JUMPDEST callvalue + 0a 0x19 0x000001 15