WEB3DEV Español

Juliana Cabeza
Juliana Cabeza

Posted on

Serie de Rayos X de Solidity: Dominando el Almacenamiento — Parte Final

Image description

https://blog.angle.money/playing-with-yul-cd4785e456d8

Vamos entender los conceptos avanzados del Almacenamiento de Contratos usando Yul con la ayuda del siguiente código de ejemplo.

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract StroageYul {

   uint256 a = 1;
   uint128 b = 2;
   uint120 c = 3;
   uint8 d = 4;

   function readStorageSlot() public view
   returns(
   bytes32 valueAtSlotZero,
   bytes32 valueAtSlotOne,
   bytes32 valueAtSlotTwo,
   uint256 slotB,
   uint256 slotC,
   uint256 slotD,
   uint256 offsetB,
   uint256 offsetC,
   uint256 offsetD) {


       assembly {
            valueAtSlotZero := sload(0)  // valor del slot real en bytes <- sload(NúmeroDelSlot)
            valueAtSlotOne := sload(1)   // sload(var_name.slot)
            valueAtSlotTwo := sload(2)
            slotB := b.slot                 // uint256 <- nombre_var.slot (no el valor real)
            slotC := c.slot
            slotD := d.slot
            offsetB := b.offset             // uint256 <- nombree_var.offset (no el valor real)
           offsetC := c.offset
           offsetD := d.offset
       }       
   }

   function getC_ValueBySlotAndOffset() external view returns (uint256 cValue) {
       assembly {
           let cSlotPackedBytes32:= sload(c.slot)
           let cValueShiftR_Bits128 := shr(128, cSlotPackedBytes32)
           cValue := and(0xff, cValueShiftR_Bits128)
       }   
Enter fullscreen mode Exit fullscreen mode

Sección #1 – .slot y .offset

Salida de la función readStorageSlot():

Image description

Las variables de estado b = 2, c = 3, d = 4 fueron compactadas por Solidity en el mismo slot (marcadas en rojo).

Puede leer más sobre la compresión de variables de estado aquí.

.slotB := b.slot
Enter fullscreen mode Exit fullscreen mode

El operador .slot se sufija con el var_name, como en b, para devolver el número de slot de almacenamiento del contrato donde se guarda el valor real al que hace referencia la variable b.

En este caso, todas las 3 variables se almacenan únicamente en el slot #1.

Image description

Pero... El offset para las 3 variables es diferente.

El Offset se devuelve utilizando el operador .offset (consulte el código anterior).

offsetB := b.offset

Enter fullscreen mode Exit fullscreen mode

Pero, ¿qué es offset(desplazamiento)?

Offset = número de bytes comenzando desde la izquierda en una palabra de 32 bytes.

El Offset comienza con el número 0, no 1, y llega hasta 31, que es el último byte de 32.

b=2 tiene un offset=0 ya que está al comienzo de nuestra palabra de valor bytes32 en el slot n.° 1.

c = 3 tiene un offset = 16, ya que el valor de b = 2 ocupa los primeros 16 bytes (0-15) del espacio, siendo de tipo uint128 (128 bits = 16 bytes).

d = 4 tiene un offset = 31 (último byte de 32) porque el valor de c = 3 ocupa 15 bytes de espacio, siendo de tipo uint120 (120 bits = 15 bytes).

Observa que los tipos definidos por el usuario, complejos o no elementales, como structs, mapeos, arrays, etc., que obtienen punteros de almacenamiento o referencias de almacenamiento dentro del cuerpo de la función siempre tienen un offset cero, ya que tales tipos no pueden compactarse con otras variables de almacenamiento.

Sección #2 - Desplazamiento y Máscara Bitwise

Una limitación al usar _sload(slot#), es que proporciona un valor compactado en bytes para las variables de estado que se comprimen con diferentes valores de desplazamiento en el mismo slot.

No se puede obtener el valor de una única variable utilizando sload(slot#).

Si intentamos ejecutar el siguiente fragmento de código para obtener el valor de una variable específica c dentro del slot, obtendremos un error.

function getCValueInSlot() external view returns (uint256 cValue) {
       assembly {
           cValue := sload(c)
       }
   }


Enter fullscreen mode Exit fullscreen mode

Image description

No obstante, esto es posible utilizando Desplazamiento y Máscara Bitwise.

function getC_ValueBySlotAndOffset() external view returns (uint256 cValue) {
       assembly {
           let cSlotPackedBytes32 := sload(c.slot)
           let cValueShiftR_Bits128 := shr(128, cSlotPackedBytes32)
           cValue := and(0xff, cValueShiftR_Bits128)
       }    
   }
Enter fullscreen mode Exit fullscreen mode

Aquí estamos intentando obtener el valor de la variable c dentro del slot # 1.

Te explicaré cada paso.

(1). sload(c.slot)

cSlotPackedBytes32 = 0x04
                    000000000000000000000000000003
                    00000000000000000000000000000002
Enter fullscreen mode Exit fullscreen mode

(2). shr(128, cSlotPackedBytes32)

Él (a través de bitwise) desplaza el valor de los bytes en cSlotPackedBytes32 hacia la derecha por 128 bits (16 bytes).

cValueShiftR_Bits128 =  0x0000000000000000000000000000000004
                        000000000000000000000000000003

Enter fullscreen mode Exit fullscreen mode

Hice una pequeña edición en el código de ejemplo anterior para que este valor intermedio muestre el resultado.

¿Por qué desplazamos hacia la derecha por 16 bytes?

Hicimos esto usando shr().

b = 2 ocupa los primeros 16 bytes desde la izquierda hasta el principio y luego viene c = 3.

Al desplazar (los primeros) 16 bytes hacia la derecha, llevamos c = 3 exactamente al principio de la izquierda.

Además, observe cómo se han rellenado ceros adicionales en el lado derecho de d = 4 y permanecerá así, y eso es bueno para nuestro caso.

(3). and(0xff, cValueShiftR_Bits128)

Aquí viene el (bitwise) and.

Multiplicamos el valor desplazado en cValueShiftR_Bits128 por 0xff.

0xff & 0x0000000000000000000000000000000004
    000000000000000000000000000003

= 0x000000000000000000000000000000000000000000000000000000000000003

Enter fullscreen mode Exit fullscreen mode

Devuelve 0x03 (observa detenidamente, no hay un 4 en el resultado).

Así es como enmascaramos todos los valores excepto el que estamos buscando, es decir, 0x03.

Una forma más fácil de entender la máscara es pensar en 0xff como 11 (No once), simplemente UnoUno.

Cualquier cosa que multipliques por 1 será lo mismo.

Usé 0xff para mantener las cosas simples cuando, idealmente, sería.

0x0x0000000000000000000000000000000000000000000000000000000000000ff
Enter fullscreen mode Exit fullscreen mode

Por lo tanto, todos los dígitos excepto los 2 más a la izquierda (03), se multiplican por 0 y se convierten en 0, y nos quedan solo:

0x000000000000000000000000000000000000000000000000000000000000003
Enter fullscreen mode Exit fullscreen mode

Parece mucho para asimilar.

Pero no se apresure, vuelva a leerlo O, lo mejor de todo, ejecute el código en RemixIDE y vea los resultados tú mismo ajustando los valores de entrada.

Seguro que lo dominarás.

Aprendizajes diversos:

(1). No es posible asignar valores explícitamente a el slot y el offset de una Variable de Estado en Assembly.

function assignValues() external {
       assembly {
           c.slot := 1
           c.offset := 16
       }
   }
Enter fullscreen mode Exit fullscreen mode

Image description

(2). Una advertencia al usar sstore():

function setValueBySlotNumberAndVal(uint256 slotNumber, uint256 value) external {
       assembly {
           sstore(slotNumber, value)
       }
   }
Enter fullscreen mode Exit fullscreen mode

Este fragmento de código puede alterar los valores de las Variables de Estado en todos los slots que se deseen al interactuar con la función setValueBySlotNumberAndVal().

Ten mucho CUIDADO al usar este tipo de código en tu Contrato Inteligente, ya que puede provocar cambios no deseados en los valores de las Variables de Estado. Por esta razón, generalmente no se recomienda, ya que podría añadir una vulnerabilidad.

Sin embargo, sigo aprendiendo más sobre los casos de uso útiles de sstore() y seguiré actualizando esta sección a medida que avance.

Si encuentras fuentes relevantes para aprender más sobre esto, no dudes en compartirlas.

¡Gracias! :)

Esto concluye la parte final.

Y como siempre, agradecería mucho si pudieras dejarme una opinión honesta o comentarios constructivos sobre este artículo para que pueda mejorarlo.

Hasta la próxima, ¡feliz codificación (en Solidity)!

Bibliografía

https://docs.soliditylang.org/en/v0.8.17/yul.html#evm-dialect

Artículo original publicado por Manu Kapoor. Traducido por Juliana Cabeza.

Discussion (0)