WEB3DEV Español

Cover image for Cómo Solidity Valida Argumentos de Función. Una profundización de Yul [Avanzado]
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Cómo Solidity Valida Argumentos de Función. Una profundización de Yul [Avanzado]

Image description

Originalmente publicado en zorz.substack.com

Si no puedes responder la siguiente pregunta, con confianza y explicar por qué, aprenderás algo desde este artículo.

Para el siguiente contrato de Solidity, qué hace name() luego que setName() es invocado en la prueba, abajo en el contrato:

Image description

Los usuarios puede que vean esta prueba y piensen “espera un momento, dos argumentos son proveídos por una función que sólo debería tomar 1 argumento. ¡Esto debería fallar!”
Bueno, no lo hace. Entendamos por qué.

Solidity, como lenguaje, hace mucho endulzamiento sintáctico por nosotros, así que necesitamos entender qué está sucediendo debajo del capó. Recuerda que, la Máquina Virtual de Ethereum (EVM) sólo le preocupa las permutaciones de opcodes y precompila. La mejor forma de entender qué está siendo interpretado por el EVM sin pisar sobre los bytecode es ver a la representación Yul del contrato inteligente. En Foundry, podemos hacer lo siguiente:

Image description

Esto hará la salida del siguiente código Yul, el cual nos da un mejor vistazo sobre qué está sucediendo:

Image description

En el primer código del bloque, tenemos el código init. Mirándolo por encima, impone que el constructor no es pagable, revisando que no hay callvalue(). Luego regresa al código de ejecución, que tiene la lógica central.

Este código de ejecución es el bucle de la lógica central que será ejecutada cada vez que alguien intente hacer una call a este contrato inteligente. En el código de arriba, está etiquetado como el objeto MockPresetA_22875_deployed.

Veamos los pasos de los componentes claves del código de ejecución:

Image description

Memoryguard es usado por el optimizador Yul, diciéndole al optimizador que esta aplicación promete sólo usar el rango de la memoria, comenzando con 0x80.

0x80 es elegido porque los primeros cuatro slots de 32 bits son reservados para solidity.

Image description

El siguiente bloque del código es revisar calldata.

Image description

Este bloque de código revertirá si el tamaño del calldata es menor que cuatro bits.

¿Por qué cuatro? Solidity deriva el selector de función desde los primeros cuatro bits del hash keccak de la firma de la función.

Para nuestro contrato inteligente original, esto puede ser derivado explícitamente como:

Image description

Si tenemos una función fallback en nuestro contrato inteligente original, no revisaríamos inmediatamente que el tamaño de calldata fue deal menos 4 bits, ya que existiría una función en el contrato inteligente que no requiera al menos 4 bits de calldata. Sin embargo, en nuestro contrato inteligente MockPresetA, cada función de llamada posible requiere selector de función, que es por lo que revertimos si no existe.

Luego, profundicemos en el bloque if(...)

Image description

Rastreamos el valor 0 en la variable _2 como una optimización ya que el valor 0 es usado varias veces en el bloque del código.

Luego extraemos el selector de función de 4 bits desde el calldata. calldataload(0) nos dará una carga útil de 32 bits, comenzando desde el index 0 del calldata.

Cambiando esta carga útil a 28 bits (224 en decimales), podemos obtener los primeros 4 bits de la carga útil de 32 bits que, por su puesto, es el selector de función.

Ahora que tenemos el selector de función, coincidimos con las posibles funciones que el usuario puede estar tratando de invocar. Recuerda el cálculo del selector de función que hicimos antes. Si el usuario está intentando llamar name(), el selector de función será 0x06fdde03. Si están intentando llamar a setName(bytes32, el selector de función será 0x5ac801fe.

Centrémonos en 0x5ac801fe, ie. setName(bytes32).

Si el callvalue es no-cero, lo revertimos. Esto es porque la función no está marcado como pagable. Luego tendremos esta complicada línea

Image description

Trabajando por fuera, no (3) es igual a -4, cuando es convertido usando elcomplemento a dos.

Image description

Luego en la sección add(calldatasize(), -4), estamos reduciendo el tamaño de calldata por 4, y revisando si el resultado es menor que 32. Si realmente es menor que 32, lo revertimos.

Esto es, esencialmente, un cheque para asegurarnos que tenga, AL MENOS, un argumento de 32 bits. Esta es la razón por la cual revertiríamos si no hemos proveído ningún argumento a la función de llamada setName.
Por último, tomamos la carga útil de 32 bits comenzando luego del cuarto bit (para excluir el selector de función) y guarda eso en el slot de almacenamiento 0 usando sstore antes de regresar.

La cosa interesante a tomar en cuenta es que cualquier bit adicional luego de los primeros 36 bits ( el selector de función + el argumento de 32 bits) son completamente ignorados.

Viendo la pregunta original, veamos si podemos tener más confianza con nuestra respuesta. ¿Qué hace name() que regresa nuestra prueba?

Image description

Por su puesto, regresará como “Hola, Mundo!(Hello World!)”. El segundo argumento es completamente ignorado. El mismo principio se aplica a los constructores de los contratos.

Gracias por leer, asegúrate de seguirnos en Twitter y suscríbete a nuestro boletín.

https://zorz.substack.com/p/how-the-solidity-validates-function-23-08-26

Originalmente publicado en zorz.substack.com

Este artículo es una traducción de Zoraiz Mahmood, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)