WEB3DEV Español

Cover image for Serie de Rayos X de Solidity: Dominando el Almacenamiento — Parte 2
Juliana Cabeza
Juliana Cabeza

Posted on • Updated on

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

Image description

https://www.trytoprogram.com/c-programming/c-programming-pointers/

Sección #1

Vamos a aprender sobre los 'Punteros/Referencias de Almacenamiento' ahora.

¿Qué son?

Simplificando, son 'punteros' que señalan al propio almacenamiento de un contrato.

Bien, 'apuntar al propio almacenamiento de un contrato' merece una explicación adicional.

function setStudentData(StudentData storage studentData) public {
       // código
   }
Enter fullscreen mode Exit fullscreen mode

Los punteros de almacenamiento se 'crean' utilizando la palabra clave '
storage junto al nombre de una variable en la firma de la función, como se muestra en studentData arriba.

Esa función solo puede ser utilizada en una biblioteca de Solidity y no en un contrato.

Considera el código a continuación:

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

contract StoragePointers {

   struct StudentData {
       uint256 id;
       string name;
   }

   function setStudentData(StudentData storage studentData) public {
       // código
   }
}

Enter fullscreen mode Exit fullscreen mode

Esto genera un error de compilación:

Image description

Es porque, por su diseño, Solidity no permite que una función de contrato acepte ningún puntero/referencia de almacenamiento como su parámetro.

En cambio, Solidity permite, por su diseño, que sus bibliotecas acepten punteros de almacenamiento como parámetros en sus funciones.

Por lo tanto, el código a continuación compila.

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

library StoragePointers {

   struct StudentData {
       uint256 id;
       string name;
   }

   function setStudentData(StudentData storage studentData) public {
       // código
   }
}

Enter fullscreen mode Exit fullscreen mode

Una discusión perspicaz sobre este aspecto del lenguaje se encuentra aquí, en caso de que desee explorar más sobre esto.

Recuerda que el almacenamiento/variables en un contrato se crean únicamente en la implementación del contrato, de la manera en que se declaran en el código.

No tiene sentido crear nuevas variables de almacenamiento durante una llamada de función de contrato cuando esas variables deberían persistir.

Existen otros aspectos relacionados con esta característica específica de Solidity que aún estoy explorando.

Si encuentras fuentes creíbles y relevantes donde pueda obtener más información sobre este tema complejo, por favor, deja un comentario. Gracias.

Sección #2

Considera el código de ejemplo a continuación:

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

library StoragePointers {

    struct StudentData {
        uint256 id;
        string name;
    }
    // para leer -> memória
    function getStudentData(StudentData memory studentData) public pure returns (uint256){
        return studentData.id;
    }
    // para escribir -> almacenamiento
    function setStudentData(StudentData storage studentData, uint256 _id) public {
        studentData.id = _id;
    }
}

contract StorageP {

    using StoragePointers for *;

    // StoragePointers.StudentData _studentData;

    function setDataOfStudents(uint256 _id) public pure {
        StoragePointers.StudentData memory _studentData;
        _studentData.setStudentData(_id);
    }

   // function setDataOfStudents(uint256 _id) public {
   //     _studentData.setStudentData(_id);
   // }
}

Enter fullscreen mode Exit fullscreen mode

La función setStudentData() de la biblioteca StoragePointers solo acepta una variable de tipo puntero/referencia de almacenamiento como su argumento, junto con otro uint256 _id.

Por lo tanto, la función setDataOfStudents() del contrato StorageP generará un error de compilación cuando se invoque utilizando el tipo struct _studentData, ya que el tipo struct _studentData se declara en la memoria.

Image description

Ahora, el código revisado al descomentar la parte comentada en el contrato de hecho compila. Velo a continuación:

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

library StoragePointers {

    struct StudentData {
        uint256 id;
        string name;
    }
    // para leer -> memória
    function getStudentData(StudentData memory studentData) public pure returns (uint256){
        return studentData.id;
    }
    // para escribir -> almacenamiento
    function setStudentData(StudentData storage studentData, uint256 _id) public {
        studentData.id = _id;
    }
}

contract StorageP {

    using StoragePointers for *;

    StoragePointers.StudentData _studentData;

    // function setDataOfStudents(uint256 _id) public pure {
    //  StoragePointers.StudentData memory _studentData;
    //  _studentData.setStudentData(_id);
    // }

    function setDataOfStudents(uint256 _id) public {
        _studentData.setStudentData(_id);
    }
}


Enter fullscreen mode Exit fullscreen mode

Aquí, dado que _studentData es una variable de almacenamiento del contrato, puede invocar setStudentData(_id).

Aprendizaje:

Una función (de una biblioteca) sólo aceptará una referencia de almacenamiento como parámetro si se declara de esa manera, y esta declaración solo puede ocurrir en una biblioteca, según el diseño de Solidity, y no en un contrato.

Esta biblioteca puede ser importada y reutilizada en un contrato para lograr esta funcionalidad dentro del contrato.

Sección #3

¿Has leído sobre Pasar por Valor vs. Pasaje por referencia?

Usaré esta analogía aquí.

En términos simples, Paso por Valor significa que creas otra variable asignando el valor de la primera variable. De esta manera, tienes 2 copias (variables) del mismo valor.

Los cambios en cualquiera de los valores no afectarán a la otra copia.

Paso por Referencia, en su contraparte, implica que cuando asignamos la referencia (al valor en sí) a otra variable, la nueva variable aún se refiere/apunta sólo al valor original.

Esto significa que ambas variables de referencia apuntan al mismo valor.

Y, por lo tanto, los cambios realizados en el valor al acceder a cualquiera de las referencias se reflejarán claramente en la otra también.

Abordaré los 2 escenarios anteriores utilizando un código de ejemplo a continuación.

Escenario #1

Al asignar una variable de almacenamiento a una variable local/en memoria en una función, esto hace que se creen dos copias diferentes de un valor.

Escenario #2

Al asignar una variable de almacenamiento a una variable de tipo referencia de almacenamiento en una función, esto hace que se cree otra referencia al almacenamiento, lo que significa que do

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

library StoragePointers {

    struct StudentData {
        uint256 id;
        string name;
    }
    // para leer -> memória
    function getStudentData(StudentData memory studentData) public pure returns (uint256){
        return studentData.id;
    }
    // para escribir -> almacenamiento
    function setStudentData(StudentData storage studentData, uint256 _id) public {
        studentData.id = _id;
    }
}

contract StorageP {

    using StoragePointers for *;

    StoragePointers.StudentData _studentDataSto;

    // variável de armazenamento: mapeamento
    // uint256: 'id' aponta para um estudante específico
    mapping(uint256 => StoragePointers.StudentData) public studData;

    function setDataOfStudentsStorage(uint256 _id) public {
        _studentDataSto.setStudentData(_id);
    }
    // ======================
    // Analogía: Passa por VALOR
    function returnMemory() public view returns (uint256) {
        StoragePointers.StudentData memory _studentDataMem;
        _studentDataMem.id = _studentDataSto.id;    // 2 cópias criadas
        return (_studentDataMem.id + 1);  
        // "+1"  en `return` NO afecta el valor original del "id" de la variable de almacenamiento "_studentData"    
    }

    function returnStorage() public view returns (uint256) {
        return _studentDataSto.id;
    }
    // ======================

    // Analogía: Pasa por REFERENCIA
    function setDataOfStudentsUsingStorageRef(uint256 _id, string memory _name) public {
        // ambos el mapeo con el índice (id) y el newStudentData apuntan al mismo lugar de almacenamiento
        StoragePointers.StudentData storage newStudentData = studData[_id];
        newStudentData.id = _id;
        newStudentData.name = _name;
    }
}

Enter fullscreen mode Exit fullscreen mode

Escenario #1

Después de desplegar el contrato, ejecuta setDataOfStudentsStorage(uint256 _id) proporcionando _id = 5 como argumento de entrada.

Ahora, ejecuta ambas las funciones: returnMemory() y returnStorage() una tras otra.

Obtendrás la siguiente salida:

Image description

Consulta los comentarios escritos junto al código anterior para una mejor comprensión.

La línea de código a continuación creó 2 copias diferentes del mismo valor de almacenamiento.

_studentDataMem.id = _studentDataSto.id
Enter fullscreen mode Exit fullscreen mode

Es por eso que return (_studentDataMem.id + 1); devolvió 6, mientras que return _studentDataSto.id; solo devolvió 5.

Escenario #2

Ejecuta la función setDataOfStudentsUsingStorageRef() e ingrese _id = 1 y _name = Manu.

Luego, verifica los valores utilizando el mapeo studData[id = 1] => "Manu":

Image description

La línea de código a continuación crea una nueva referencia de almacenamiento (newStudentData) para el mismo valor de almacenamiento que ya está siendo referenciado por el mapeo studData[_id] en algún lugar de almacenamiento en el contrato.

StoragePointers.StudentData storage newStudentData = studData[_id];
Enter fullscreen mode Exit fullscreen mode

Exacto, es por eso que el mapeo proporciona la salida de id = 1 y name = Manu, ya que la transacción de setDataOfStudentsUsingStorageRef() realizó cambios directamente en el almacenamiento del contrato.

Uf... lo sé, realmente fue mucho para asimilar.

Recomiendo encarecidamente que ejecutes los mismos fragmentos de código que he creado para tu comprensión en el Remix IDE, ingresando diferentes valores para ver las salidas.

Esto concluye la parte 2.

Y, como siempre, estaré agradecida si puedes proporcionar una retroalimentación honesta, reacción y/o crítica constructiva sobre este artículo para que pueda mejorarlo.

En la próxima y última parte, explicaré algunos conceptos avanzados de almacenamiento utilizando Yul y te convertiré en un profesional.

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

Bibliografía:

https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html?source=post_page-----84415fb11e60--------------------------------
https://betterprogramming.pub/all-about-solidity-data-locations-part-i-storage-e50604bfc1ad?source=post_page-----84415fb11e60--------------------------------
https://coinsbench.com/storage-pointers-in-solidity-4a2d7c55a054#:~:text=Photo%20by%20Jefferson%20Santos%20on,a%20specific%20value%20or%20variable
https://forum.soliditylang.org/t/storage-across-contracts/352?source=post_page-----84415fb11e60--------------------------------

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

Discussion (0)