Mappings en Solidity

Solidity

En este tutorial vamos a ver cómo crear y manipular mappings en Solidity. Veremos también los usos más habituales de estas estructuras junto con algunos ejemplos en los que cubriremos las operaciones más comunes que se suelen realizar con estas estructuras.

Qué es un mapping

Los mappings son tablas que se componen de pares de clave y valor, al igual que los objetos literales de lenguajes como JavaScript. Permiten organizar valores de forma estructurada..

En el fondo, la estructura de un mapping no difiere mucho de una tabla hash o diccionario:

{
    a: 1,
    b: 2,
    ...
}

Las claves de un mapping pueden ser de cualquiera de los tipos que soporta Solidity, salvo aquellos que contienen referencias a otros elementos. Los valores, por el contrario, pueden ser de cualquiera de los tipos existentes en Solidity, por lo que, como ya veremos, también es posible crear mappings de arrays e incluso mappings de otros mappings.

A diferencia de los arrays, usando mappings es mucho más sencillo obtener valores concretos del interior de la estructura, ya que podremos personalizar las claves asociadas a los mismos. Para obtener un valor, bastará con conocer la clave del mismo.

Estas estructuras se suelen usar para asociar ciertos valores a las direcciones de Ethereum gestionadas por el Smart Contract.

Cómo declarar mappings

Para declarar un mapping tendrás que usar la sentencia mapping. Luego, entre paréntesis, tendrás que indicar el tipo de la clave, usar el operador => y luego indicar el tipo de valor que contendrá la estructura. Seguidamente tendrás que definir opcionalmente la visiblidad de la estructura para luego agregar el identificador del mapping, que tendrás que agregar en cualquier caso:

mapping(clave => valor) <modificador_acceso> <identificador>;

En el siguiente ejemplo creamos un mapping que nos permitirá asociar balances a direcciones de Ethereum. Por ello, el tipo de la clave será de tipo address y el tipo de valor de tipo uint:

mapping(address => uint) balances;

Vamos a crear un Smart Contract y a definir una variable de tipo mapping en su interior que modificaremos más adelante:

pragma solidity ^0.8.0;

contract MiContrato
{
    mapping(address => uint) balances;
}

Operaciones CRUD con mappings

A continuación vamos a ver cómo agregar, leer, actualizar o eliminar elementos de un mapping.

Cómo agregar un elemento a un mapping

Para agregar un elemento a un mapping basta con usar el identificador de la estructura y definir la clave del elemento a agregar entre corchetes, asignándole seguidamente un valor.

Continuando con el ejemplo del apartado anterior, vamos a asignarle el valor 10 a una dirección de Ethereum:

balances[msg.sender] = 10;

Cómo leer un elemento de un mapping

Para leer un elemento de un mapping tendrás que hacer referencia a su identificador y también a la clave en la que se encuentra dicho valor:

balances[msg.sender];

Cómo actualizar un elemento de un mapping

Para actualizar un elemento de un mapping tendrás que asignar un valor al elemento del mapping indicado mediante su identificador y la clave del valor a actualizar:

balances[msg.sender] = 20;

Cómo eliminar un elemento de un mapping

Para eliminar un elemento de un mapping tendrás que usar la sentencia delete seguida del identificador del mapping y de la clave del elemento a eliminar entre corchetes:

delete balances[msg.sender];

Valores por defecto de un mapping

Los mapping tienen ciertas diferencias con respecto a los literales de JavaScript. Cuando en JavaScript haces referencia a un elemento de la estructura que no existe, obtendrás undefined como resultado. Por poner otro ejemplo, en PHP obtendrás null como resultado so haces referencia a un elemento inexistente de un array asociativo.

Pues bien, este comportamiento no se da en Solidity, ya que siempre que hagas referencia a un elemento inexistente de la estructura obtendrás el valor por defecto de dicho tipo. En tipos numéricos como uint, obtendrás el valor 0 como resultado. En el siguiente ejemplo obtendremos 0 como resultado:

balances[elementoInexistente]; // 0

Usando mappings anidados

Un mapping anidado no es otra cosa que un mapping cuyos valores contienen otros mappings, conformando una matriz como resultado. Para declarar un mapping anidado, tendrás que definir otro mapping como tipo de valor del mapping que quieres crear.

En el siguiente ejemplo definimos un mapping anidado:

mapping(address => mapping(address => resultados)) probados;

Esta estructura puede ser útil en la implementación de tokens ERC-20, ya que permite establecer un sistema de aprobación, de modo que alguien que disponga cierta cantidad de tokens pueda permitir que otra dirección pueda gastar tokens en su nombre. La primera dirección del ejemplo se correspondería con la del dueño de los tokens, mientras que la segunda sería la dirección que ha recibido la aprobación para gastar tokens en su nombre. Mediante las variables booleanas simplemente establecemos si las direcciones referenciadas han sido aprobadas o no.

No solamente podrás anidar dos mappings, sino todos los que desees, pudiendo así crear matrices de valores de varias dimensiones.

Vamos a definir el siguiente contrato para luego ver cómo realizar operaciones sobre este tipo de mapping:

pragma solidity ^0.8.0;

contract MiContrato
{
    mapping(address => mapping(address => bool)) aprobaciones;
}

Cómo agregar un elemento a un mapping anidado

Para agregar un nuevo elemento a un mapping anidado deberías referenciar el elemento que deseas actualizar del mapping principal mediante el identificador del mapping y la clave a actualizar. Seguidamente también deberás referenciar entre corchetes la clave del elemento que deseas actualizar, asignándole un valor:

function aprobar(address direccion) external {
    aprobaciones[msg.sender][direccion] = true;
 

Cómo leer un elemento de un mapping anidado

Para leer un elemento sencillamente tendrás que usar identificador del primer mapping seguido del identificador del primer mapping entre corchetes y también del segundo entre corchetes:

function estado(address direccionConPermiso) external view returns (bool) {
    return aprobaciones[msg.sender][direccionConPermiso];
}

Cómo actualizar un elemento de un mapping anidado

Para actualizar un elemento tendrás que hacer referencia al elemento del mapping  a actualizar, como si lo fueses a leer, para luego asignarle un nuevo valor:

function desaprobar(address direccionConPermiso) external {
    aprobaciones[msg.sender][direccionConPermiso] = false;
}

Cómo borrar un elemento de un mapping anidado

Para eliminar un elemento tendrás que usar la sentencia delete seguida del identificador del mapping y del árbol de claves de los diferentes mappings anidados, tal y como hemos hecho en los casos anteriores:

function eliminar(address direccionConPermiso) external {
    delete aprobaciones[msg.sender][direccionConPermiso];
}

Usando mappings de arrays

No solamente puedes crear mappings de otros mappings, sino que también puedes crear mappings que contengan arrays. Para definirlos sencillamente debes indicar el tipo del array en la declaración del tipo de valor del mapping. Por ejemplo, vamos a definir un mapping que contenga un array con valores de tipo uint:

mapping(address => uint[]) valores;

Al igual que hemos hecho en los casos anteriores, vamos a definir un Smart Contract que contenga un mapping de este tipo para luego ver cómo realizar operaciones CRUD con nuestro mapping de ejemplo.

pragma solidity ^0.8.0;

contract MiContrato
{
    mapping(address => uint[]) valores;
}

Cómo agregar un elemento a un mapping de arrays

El mapping estará inicialmente vacío. Para agregar un array al mismo, en teoría tendrás que crearlo usando la sentencia new:

creeditos[msg.sender] = new uint[](2);

Sin embargo, este paso no será necesario, ya que el array estará ya instanciado tras la definición del mapping, por lo que podrás agregar elementos directamente al mismo mediante la sentencia push:

function agregarValor() external {
    valores[msg.sender].push(1);
}

Cómo leer un elemento de un mapping de arrays

Si quieres leer un elemento de un array del mapping, tendrás que referenciar el array que quieres leer usando la clave del mismo para luego indicar el índice del elemento que quieres obtener del array:

function obtenerValor(uint posicion) external view returns (uint) {
    return valores[msg.sender][posicion];
}

Cómo actualizar un elemento de un mapping de arrays

Para actualizar un elemento bastará con que asignes directamente un valor a la posición deseada del array, que podrás seleccionar mediante su índice:

function actualizarValor(uint posicion, uint valor) external returns (uint) {
    return valores[msg.sender][posicion] = valor;
}

Cómo borrar un elemento de un mapping de arrays

Para eliminar un elemento del array tendrás que usar la sentencia delete, indicando la posición del array a eliminar:

function eliminarValor( uint posicion) external {
    delete valores[msg.sender][posicion];
}

Limitaciones de los mappings

Seguramente te estés preguntando si existe algún modo más apropiado de trabajar con estructuras que sean más complejas, pudiendo así tener estructuras mejor definidas como si de una clase al uso se tratase. Para este propósito suelen usarse las estructuras, que veremos en otro artículo.

Esto ha sido todo.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”