Saltar al contenido
0%

¿Cómo funciona la memoria en Yul?

Mark Jonathas headshot

Escrito por Mark Jonathas

Publicado el 28 de julio de 20235 min de lectura

Yul es un lenguaje de programación intermedio que se puede usar para escribir una forma de lenguaje ensamblador dentro de smart contracts. Una vez que hayas aprendido sobre el storage de Yul y cómo leer y escribir variables de storage empaquetadas, es importante aprender cómo funciona la memoria en los smart contracts de Yul.

¿Cómo funciona la memoria en yul?

La memoria se comporta de manera distinta al storage. La memoria no es persistente, lo que significa que una vez que la función termina de ejecutarse, todas las variables se borran.

La memoria es comparable al heap en otros lenguajes, pero no hay un garbage collector.

La memoria es mucho más barata que el storage. El costo de las primeras 22 palabras de memoria se calcula de forma lineal, pero ten cuidado porque después de eso el costo de la memoria se vuelve cuadrático.

La memoria se organiza en secuencias de 32 bytes. Vamos a entender esto mejor más adelante, pero por ahora ten en cuenta que 0x00 - 0x20 es una secuencia (puedes pensarlo como un slot si te ayuda, pero son cosas diferentes).

Solidity asigna 0x00 - 0x40 como scratch space. No hay garantía de que esta área de memoria esté vacía, y se usa para ciertas operaciones.

0x40 - 0x60 almacena la ubicación de lo que se conoce como el free memory pointer, que se usa para escribir algo nuevo en memoria.

0x60 - 0x80 se deja vacío como un espacio en blanco.

0x80 es donde empezamos nuestras operaciones.

La memoria no empaqueta valores. Los valores obtenidos del storage se almacenan en su propia secuencia de 32 bytes (es decir, 0x80-0xa0).

¿Qué operaciones usan memoria en yul?

La memoria se usa para las siguientes operaciones:

  • Valores de retorno de llamadas externas
  • Establecer valores de función para llamadas externas
  • Obtener valores de llamadas externas
  • Revertir con un mensaje de error
  • Registrar mensajes (log)
  • Hashear con keccak256()
  • Crear otros smart contracts

Aquí hay algunas instrucciones de Yul útiles para la memoria:

Instruction
Explanation

mload(p)

Similar a sload(), pero aquí decimos que cargue los siguientes 32 bytes después de p

mstore(p, v)

Similar a sstore(), pero aquí decimos que almacene el valor v en p más 32 bytes

mstore8(p, v)

Similar a mstore(), pero solo para un único byte

msize()

Devuelve el índice de memoria más grande accedido

pop(x)

Descarta el valor x

return(p, s)

Finaliza la ejecución y devuelve los datos de las ubicaciones de memoria p - v

revert(p, s)

Finaliza la ejecución sin guardar los cambios de estado, y devuelve los datos de las ubicaciones de memoria p - v

¡Veamos algunas estructuras de datos más!

Cómo usar structs y memoria en yul

Los structs y los arrays de tamaño fijo en realidad se comportan igual, pero como ya vimos los arrays de tamaño fijo en la sección de storage, ahora veremos los structs. Observa el siguiente struct.

No hay nada inusual aquí, solo un struct simple.

Ahora veamos algo de código.

Aquí estamos estableciendo s.subVar1 en la ubicación de memoria 0x80 - 0xa0 y s.subVar2 en la ubicación de memoria 0xa0 - 0xc0. Por eso estamos devolviendo 0x80 - 0xc0. Aquí hay una tabla del layout de memoria justo antes del final de la transacción.

Memory Location
Value Stored

0x00

Scratch Space (Empty)

0x20

Scratch Space (Empty)

0x40

0xc0 (Free Memory Pointer)

0x60

Empty

0x80

s.subVar1: 0x20 (32 en decimal)

0xa0

s.subVar1: 0x40 (64 en decimal)

0xc0

Nuevo Free Memory Pointer. Esto es lo que devolvería msize(). (Empty)

Cosas a tener en cuenta:

  • 0x00 - 0x40 está vacío para el scratch space
  • 0x40 nos da el free memory pointer
  • Solidity deja un espacio vacío en 0x60
  • 0x80 y 0xa0 se usan para almacenar los valores del struct
  • 0xc0 es el nuevo free memory pointer

En esta última parte de la sección de memoria quiero mostrarte cómo funcionan los arrays dinámicos en memoria. Vamos a pasar [0, 1, 2, 3] como el parámetro arr en este ejemplo. Como bonus adicional para este ejemplo, vamos a agregar un elemento extra al array. Ten cuidado al hacer esto en producción, ya que podrías sobrescribir otra variable de memoria.

Aquí está el código:

Esto es lo que está pasando:

  • Obtener dónde está almacenado el array en memoria
  • Obtener la longitud del array, que se almacena en la primera ubicación de memoria del array
  • Sumar 32 bytes a la ubicación (saltando la longitud del array) para ver la siguiente ubicación disponible
  • Multiplicar la longitud del array por 32 bytes para avanzar a la siguiente ubicación de memoria
  • Almacenar nuestro nuevo valor (4)
  • Actualizar la longitud del array en uno
  • Actualizar el free memory pointer
  • Devolver el array

Veamos de nuevo el layout de memoria.

Memory Location
Value Stored

0x00

Scratch Space (Empty)

0x20

Scratch Space (Empty)

0x40

0x140 (Free Memory Pointer)

0x60

Empty

0x80

Nueva longitud del array (6)

0xa0

arr[0] (0)

0xc0

arr[1] (1)

0xe0

arr[2] (2)

0x100

arr[3] (3)

0x120

arr[4] (4)

0x140

Free Memory Pointer (Empty)

¡Con esto concluye la sección sobre memoria!

A continuación, aprende cómo llamar smart contracts usando Yul o vuelve a aprender cómo leer y escribir variables de storage empaquetadas.

Background gradient

Construye magia blockchain

Alchemy combina los productos y herramientas de desarrollo Web3 más potentes con recursos, comunidad y un soporte legendario.