Hola etherians!

Hoy estuve programando algunos Contratos Inteligentes en Solidity para que la comunidad pudiera adquirir entusiasmo y entender que cosas podemos hacer más o menos con Ethereum y Soldity.

Me dio nostalgia recordar que hace muchos años, cuando recién comenzaba a programar, una de las prácticas más típicas entre los programadores de los foros donde participaba, era desarrollar un libro de visitas o chat donde se almacenaban los mensajes y eran mostrados posteriormente en otra sección del sitio web. Conforme evolucionaban los lenguajes de programación, también lo hacían las formas de realizar este mismo ejercicio añadiendo una nueva tecnología o característica. En esos tiempos se tenía que enviar la información a través de un formulario que recargaba la web por cada vez que enviábamos un mensaje al chat o libro de visita, algo muy rústico para lo que es la programación hoy en día. Luego con el pasar de los meses, aparecían programadores futuristas que comenzaron a mostrar la misma práctica del libro de visitas/chat, pero esta vez los visitantes podían escribir y ver los mensajes sin tener que recargar el sitio web, esto gracias a AJAX, una tecnología muy de moda en ese entonces. Así que pensé…

¿Qué tan complicado podría ser crear un libro de visitas/chat descentralizado?

Me puse manos a la obra (código) ¡ Y mi sorpresa fue estremecedora! Realizar este ejercicio con un Contrato Inteligente programado en Solidity como Base de Datos publicada en la Blockchain Ethereum fue mil veces más sencillo que tener que crear una Base de Datos SQL tradicional y tener que interactuar con ella mediante PHP o cualquier otro lenguaje de programación. Adiós SELECT * FROM mensajes, adiós vulnerabilidades SQL Injection, adiós borrar los mensajes sin querer por no ponerle el WHERE al DELETE FROM!!


Antes de empezar…

Les recomiendo leer la documentación oficial de la librería Web3.js de Javascript, que es la que nos permitirá interactuar con la Blockchain desde el navegador. Es importante que estén un poco interiorizados con esta librería para tener un buen desempeño desarrollando los Contratos Inteligentes.


Manos al código

Esta vez haremos la siguiente práctica: Primero pondré el código y posteriormente explicaré que hace cada línea del contrato.

dMessenger.sol

Explicación línea por línea del Contrato Inteligente

Con  pragma solidity ^0.4.18;Definimos la versión del compilador de Solidity que utilizamos. En este caso la versión es la 0.4.18


Definimos el nombre del contrato con  contract dMessenger { Todo lo que esté dentro de las llaves será parte del código del contrato y la información contenida será pública o privada según como se declaren las variables.


Con address public creador; Creamos una variable pública tipo address para almacenar la dirección Ethereum del creador del contrato. Esto se suele utilizar para poder verificar en un futuro la autoría de un Contrato Inteligente o para permisionar el mismo, de modo que solo el creador (administrador) del contrato sea quién puede realizar ciertas acciones, como transferir ETHER del contrato a una dirección Ethereum (transfer())

uint public contadorMensajes = 0; Nos ayudará a llevar la cuenta de cuántos mensajes han sido publicados, además nos permitirá asociar un ID a cada mensaje, lo que nos facilitará la vida al momento de querer ver un mensaje específico sin tener que cargarlos todos.


En Solidity las estructuras se comportan como lo haría un diccionario en Python o un objeto en Javascript, pero que en el caso de este lenguaje se pre-define la estructura que tendrá el diccionario y no se puede alterar luego de creada. Para hacer uso de esta estructura tenemos que usarla como si se tratara de un tipo de dato Mensaje instancia; dónde instancia tendría todos los atributos de Mensaje:

Una forma diferente de utilizar estructuras es con mapping(tipoDeDato => Estructura) instancia; que a diferencia de Mensaje instancia; (donde solo tenemos una estructura) nos da la posibilidad de crear un diccionario donde cada clave apuntará a una estructura, quedando instancia[clave].atributoEstructura = valorAtributo; donde clave puede ser cualquier valor, siempre y cuando cumpla la condición de que sea del mismo tipo de dato que pre-definimos en el mapping: tipoDeDato. Puede ser uint, string, address, bool, bytes32 o cualquier tipo de dato existente en Solidity.

Con mapping(uint => Mensaje) public mensajes; lo que estamos haciendo es decirle a Solidity que mensajes será un diccionario donde sus claves serán única y exclusivamente números enteros sin signo o unsigned integer (uint) y que cada clave apuntará a una estructura Mensajes, por lo que cada mensajes[uint] tiene los mismos atributos:

En Javascript y Python podemos añadir o eliminar atributos a los objetos, pero en Solidity eso no es posible, ya que la estructura que contiene cada clave en el diccionario, es inmutable. No podemos hacer mensajes[contadorMensajes].nuevoAtributo = nuevoValor;, ya que nuestra estructura Mensaje al no tener el atributo nuevoAtributo arrojaría error y no compilaría el Contrato Inteligente.


Cuando una función lleva el mismo nombre que el contrato, se ejecuta solo una vez y es cuando se crea el Contrato Inteligente. Muchos de los Contratos que veamos de aquí en adelante utilizarán funciones que llevan el mismo nombre del Contrato, aprovechando que la función se ejecuta solo cuando se crea el Contrato y es el único lugar donde se le asigna un valor a la variable creador, por lo que nadie más puede podría asignar un valor diferente a esta variable, porque no existe asignación de valor a la variable creador en otra función que no sea esta. Nos ayuda a delimitar quienes pueden realizar que acciones, ya que en ciertos contratos solo quién crea el contrato es quién debería poder realizar transferencias de ETHER desde el contrato a una cuenta externa, añadir o modificar información, entre otras cosas.


La función escribirMensaje(string mensaje) como deben sospechar, nos ayuda a escribir en el contrato inteligente. Recibe como parámetro un string (llamado mensaje) que será el que se escribe en el contrato. La función es totalmente pública, por lo que puede ser llamada desde fuentes externas al contrato.

Primero que todo, msg.sender siempre tiene el valor de quién envía la transacción (o llama la función).

Bien, vamos paso por paso. Una persona envía un mensaje al contrato llamando escribirMensaje(); lo que pasa es lo siguiente:

El contrato tiene una variable contadorMensajes que es un entero sin signo/unsigned integer (uint) y su valor inicial es 0, por lo que el contrato haría:

Eso en la primera ejecución sería equivalente a:

Dónde mensajes[contadorMensajes].emisor = msg.sender; escribe en la clave contadorMensajes (0) del diccionario mensajes que el emisor es igual a quién envía la transacción.
Luego con mensajes[contadorMensajes].mensaje = mensaje; le asignamos un valor al atributo mensaje de la estructura que está alojada en la clave contadorMensajes (0) del diccionario mensajes.

now es un alias para block.timestamp, que se interpreta como “la marca de tiempo (fecha en timestamp) en la que fue minado el bloque en el que fue enviada la transacción”. No representa la fecha exacta en la que fue enviado el mensaje. La clave contadorMensajes (0) del diccionario mensajes apunta a una estructura Mensaje que tiene un atributo llamado fechaPublicación, al que le asignaremos el valor block.timestamp.

Cuando envíen otro mensaje habrá otra ejecución de la función escribirMensaje(); y contadorMensajes ahora tiene un valor distinto al inicial, ya que luego de añadir la clave contadorMensajes (0) al diccionario mensajes con sus respectivos atributos (emisor, mensaje, fechaPublicación), hicimos un contadorMensajes++;, así que ahora contadorMensajes vale 1 y el siguiente mensaje tendrá ese valor como clave en el diccionario.

Ahora el contrato tiene una variable contadorMensajes que es un entero sin signo/unsigned integer (uint) y su valor pasó a ser 1, por lo que el contrato haría:

Qué es equivalente a:

Dónde mensajes[contadorMensajes].emisor = msg.sender; escribe en la clave contadorMensajes (1) del diccionario mensajes que el emisor es igual a quién envía la transacción.
Luego con mensajes[contadorMensajes].mensaje = mensaje; le asignamos un valor al atributo mensaje de la estructura que está alojada en la clave contadorMensajes (1) del diccionario mensajes.

La clave contadorMensajes (1) del diccionario mensajes apunta a una estructura Mensaje que tiene un atributo llamado fechaPublicación, al que le asignaremos el valor block.timestamp.

Así sería sucesivamente con todos los mensajes…


Compilar y publicar el Contrato Inteligente

Para compilar y publicar nuestro Contrato Inteligente haremos uso de una aplicación web que personalmente me encanta, Remix – Solidity IDE. No requiere descargar nada más que Metamask para su correcto funcionamiento. Nos permitirá publicar contratos e interactuar con ellos muy fácilmente.

Publicar contratos en la red Ethereum conlleva un gasto en ETHER, por lo que antes de publicar algo, tenemos que fijarnos que todo esté muy bien programado, porque volver a subir un contrato nos podría salir caro… Quizás algunos no lo saben, pero existen redes de prueba dentro de la red Ethereum que nos permitirán ejecutar contratos inteligentes sin gastar ETHER real. La que utilizo siempre es Rinkeby y pueden obtener muchos ETHER gratis dando clic aquí. En la parte de la dirección deben poner la cuenta Ethereum que aparece en su plugin Metamask.

Abrimos Metamask y veremos que estamos en la Main Net, que es la red real de Ethereum, donde publicar contratos inteligentes tiene un costo real de ETHER

Damos clic en la esquina superior izquierda y seleccionamos Rinkeby

Bien, una vez seleccionada la red Rinkeby en Metamask, vamos al compilador online Remix – Solidity IDE donde inicialmente veremos un contrato inteligente que viene pre-cargado en el IDE:

En la esquina superior izquierda se ve un ” + ” que al darle clic nos permitirá editar más archivos. Le damos clic y creamos “dMessenger.sol”

Damos clic en OK y ya podemos comenzar a editar nuestro código Solidity. Pegamos el código del dMessenger y debería lucir exactamente así:

El compilador online por defecto va compilando sin preguntar, por lo que no es necesario estar dando clic a un botón de compilar para ver si arroja errores, ya que los va mostrando en tiempo real, algo que acelera bastante el proceso. Vamos a la pestaña Run que es desde dónde podremos llevar el contrato a la red de pruebas. Nos aparecerán distintos entornos (Environments) donde ejecutar nuestro Contrato Inteligente:

El que nos interesa es Injected Web3, ya que Metamask funciona inyectando Web3.js en el navegador.
Nota importante: Si Metamask está bloqueado con contraseña no podremos utilizarlo y nos aparecerá así:

Asumiendo que tenemos el Metamask desbloqueado, que estamos con Metamask en la red Rinkeby y que en la misma tenemos los ETHER obtenidos en Rinkeby.io, vamos a comenzar con la publicación del contrato inteligente. Los campos Gas Limit, Gas Price y Value los dejamos tal como están.

Nota:Para que un contrato sea publicado es necesario que quién lo publica lo haga a través de una transacción.
Damos clic en Create, lo que nos abrirá el Metamask para que firmemos la transacción y el contrato sea publicado. En la misma ventana se indican los costos de publicar el contrato en esa red, pero no nos interesa porque nuestros ETHER son gratuitos en Rinkeby 😀

Aprobamos la transacción dando clic en el botón verde que dice Submit y el contrato se publicará en la red Rinkeby de Ethereum. Esperamos unos segundos y nos aparecerá la interfaz para interactuar con las variables y funciones públicas del contrato.

Si bien es posible añadir y consultar mensajes desde esta interfaz, no es lo más óptimo, así que crearemos nuestra propia interfaz web (rústica) para este cometido.

Para poder invocar nuestro contrato inteligente desde un sitio web requerimos 2 cosas:

  1. Address: La dirección del contrato inteligente.
  2. ABI: Application Binary Interface

Para obtener la dirección del contrato damos clic en el botón de Copiar y lo apuntamos en un lugar donde no lo perdamos.

Para encontrar el ABI de nuestro contrato debemos ir a la pestaña Compile, luego dar clic en Details y buscar donde dice INTERFACE – ABI

Copiamos dando clic en el botón de Copiar y también lo apuntamos en un lugar donde podamos obtenerlo posteriormente, porque es vital para el siguiente paso, la creación de la interfaz web.


¿Cómo interactuar con el contrato inteligente desde un sitio web?

Para interactuar con el Contrato Inteligente (cargar los mensajes y poder escribir en él) requerimos 3 cosas:

  1. HTML para la interfaz gráfica: Un INPUT donde escribir el mensaje, un botón para enviarlo y un div donde cargar los mensajes enviados.
  2. Metamask: Es un plugin que nos integra un nodo Ethereum en el navegador. Nos permitirá ejecutar e interactuar con la Blockchain de Ethereum con Javascript.
  3. Javascript: Con este lenguaje de programación invocaremos Contratos Inteligentes (y todas sus funciones públicas) que vivan en la red Ethereum.

HTML de la interfaz:

¿Cómo invocar un contrato inteligente?
Como Metamask inyecta web3.js en el navegador, nosotros solo debemos utilizarlo:

En mi caso quedaría

Con este sencillo código lo que estamos haciendo es cargar todas las variables y funciones públicas del contrato (que viven en la dirección addressContrato) y cargándolas en la variable funciones.

¿Cuáles son estas variables y funciones públicas?

En el caso de nuestro contrato contamos con las variables públicas:

  • creador (address)
  • contadorMensajes (uint)
  • mensajes (string)

Las funciones públicas:

  • dMessenger
  • escribirMensaje(string mensaje)

Por lo que desde la variable funciones tendremos acceso tanto como a las funciones como a los valores de las variables, siempre y cuando sean públicas.

Nota: Quisiera destacar que para leer la información de cualquier contrato inteligente en Ethereum no tiene costo alguno, ya que no hay cambios de estados en las variables y no conlleva un gasto en GAS. Lo único que tiene costo es modificar o publicar información en los contratos y nunca se incurrirá en un gasto si tu no firmas la transacción.
Leer contrato = gratis.
Modificar contrato = cobro en ETHER.

Bien, ahora la variable funciones tiene acceso a las variables y funciones mencionadas anteriormente, pero cómo leo sus valores? Tanto para llamar funciones como para leer valores de variables requerimos de callbacks. Por ejemplo, para obtener el número de mensajes publicados:

Siempre que vayamos a consultar una variable pública tipo uint, debemos añadir .c[0] a la respuesta, porque nos devuelve un objeto cuya clave c contiene un array en el cuál el índice 0 es el que contiene la información que necesitamos.

Si quisieramos escribir un mensaje tendríamos que llamar a la función escribirMensaje(); del contrato inteligente. Eso lo hacemos de la siguiente forma:

Como escribirMensaje(); añade mensajes al contrato, estamos modificando el estado, por ende añadir mensajes nos conlleva un costo en ETHER, así que siempre que vayamos a publicar un mensaje, Metamask nos pedirá que firmemos la transacción. La respuesta de las funciones que modifican los estados del contrato siempre dan como respuesta el hash de la transacción donde fue minado.


Archivo main.js terminado y comentado para quienes tengan dudas…
Si quisieramos cargar un mensaje en específico, como el mensaje n°50, solo tendríamos que hacer
Recordemos que en nuestro código Solidity mensajes era un diccionario/mapping(); público que nos decía que las claves de éste, serían números enteros sin signo/unsigned integer (uint) y que cada clave apuntaría a una estructura Mensaje…

Si quieren ver este ejemplo funcionando en la red Rinkeby, pueden dejarnos su mensaje (es gratis!!) dando clic aquí.

Espero haya sido de su agrado y si algo no se entendió por favor dejar un comentario con la pregunta 😀

Saludos!!