Contratos inteligentes en Ethereum

Con este tutorial podrás dar tus primeros pasos en el desarrollo de contratos inteligentes en la plataforma blockchain de Ethereum.
Con un sencillo ejemplo veremos cómo se escribe un contrato, cómo se despliega, cómo se instancia en tu propia red privada de Ethereum y, lo más importante, cómo se ejecuta.

Índice de contenidos

1. Introducción

La tecnología de blockchain es aún un camino en el que hay mucho por explorar y descubrir, pero a día de hoy ya podemos hacer con ella cosas interesantes aparte de especular con las criptomonedas.

Bitcoin abrió el camino permitiendo el intercambio de criptomoneda de manera segura y descentralizada.

Ethereum va más allá y se puede considerar como una implementación de blockchain de segunda generación en la que se generaliza la idea de los intercambios entre partes.

1.1. Blockchain

En este tutorial no voy a entrar a explicar los detalles de qué es y cómo funciona un blockchain.
Sin embargo, siendo seguramente el concepto más importante de toda esta tecnología sí voy a comentar muy por encima de qué va para aquellos que aún no lo tengan claro.
Me dejo fuera muchos detalles para no desviar el foco del objetivo de este tutorial.

Un blockchain es una red para gestionar un libro de cuentas descentralizado y replicado en cada uno de los nodos que componen la red.
Este libro consiste en una cadena secuencial de bloques inmutables, que vendrían a ser las páginas del libro.
Los nuevos apuntes de transacciones se van registrando en un nuevo bloque que una vez lleno es “sellado” y añadido al final de la cadena.
Además, en todo el proceso se usan funciones criptográficas para evitar manipulación de información y garantizar la consistencia de los datos.

1.2. Ethereum

En términos generales, Ethereum se define como una plataforma blockchain de ejecución de contratos descentralizada y sin autoridad de confianza (confianza cero).

Los contratos son scripts que definen procesos desde simples intercambios de criptomoneda como los que hace Bitcoin hasta políticas complejas que se desencadenan como consecuencia de eventos.

Los contratos trabajan sobre tokens, que determinan quiénes participan en un contrato y con cuánto valor.

Tanto los participantes del contrato como el propio contrato están identificados mediante una cuenta con una dirección única. Las cuentas de participantes se llaman Externally Owned Accounts (EOA).
Además de identificar a cada uno de los elementos de la red Ethereum, las cuentas sirven de almacenamiento persistente de información.

1.3. Contratos inteligentes

Un contrato normal especifica un acuerdo entre partes con unas acciones y condiciones de cumplimiento.
Un contrato se hace “inteligente” cuando incluye código y lógica que ejecuta las acciones y fuerza la aplicación de las condiciones, es decir, código que hace que se cumpla el contrato sin necesidad de que ninguna de las partes intervenga manualmente.

Lo más interesante más allá de esa supuesta “inteligencia” es que el contrato se ejecuta dentro de la red, en la Ethereum Virtual Machine (EVM).
Esta ejecución no sale gratis sino que se paga en forma de “gas”, una pequeña cantidad de moneda ether aportada por uno o más de los participantes en el contrato.

Un detalle importante es que la ejecución es descentralizada pero no distribuida, lo cual significa que se produce en todos los nodos de la red. Esta es una de las razones por las que la EVM es tan lenta y solamente puede procesar unas 15 transacciones por segundo.

El contrato se desarrolla en un lenguaje concreto (Solidity, Serpent o Mutan), se compila a EVM y, salvo excepciones, cumple una interfaz estándar llamada ERC20.
En este tutorial usaremos Solidity.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS High Sierra 10.13.2
  • Homebrew 1.4.3
  • Geth 1.7.3
  • Ethereum Wallet 0.9.3
  • Visual Studio Code 1.19.2 con Solidity 0.0.31

3. Instalación

Obviamente no vamos a ejecutar el contrato del tutorial en la red Ethereum real, no se trata de ir tirando el dinero…
Tampoco vamos a pedir ether en ninguna de las redes de pruebas disponibles como Rinkeby o Ropsten, aunque te aconsejo que eches un vistazo a su funcionamiento.
En este tutorial montaremos nuestra propia red Ethereum privada y local con Geth.

3.1. La cartera de Ethereum

  1. Instalar la cartera de Ethereum con este comando de Homebrew:

    Si no tienes Homebrew deberás ir a https://www.ethereum.org/ y descargar e instalar el paquete para tu sistema operativo.
    Muy importante: Si ya tenías instalada la cartera de Ethereum, asegúrate de desconectarla de la red y cerrarla antes de continuar.

3.2. El nodo en la red privada

Desde una terminal:

  1. Instalar el software de línea de comando de Ethereum con Homebrew:

    Alternativamente, accede a la página https://www.ethereum.org/cli y sigue las instrucciones para tu sistema operativo.

  2. Crear el directorio donde mantendremos todos los ficheros de la red privada:

  3. Crear una cuenta de origen, introduciendo una contraseña y anotando la dirección de la cartera para añadirle después un saldo inicial:

  4. Crear otra cuenta que usaremos como destino de la ejecución de nuestro contrato:

  5. Crear el siguiente fichero de configuración genesis.json, sustituyendo <DIRECCIÓN CUENTA> por el valor obtenido en el paso 3:

    En este fichero se especifican algunos parámetros de la red privada y se añade un saldo inicial a la cuenta de origen.

  6. Inicializar la red:

  7. Arrancar la red, sustituyendo <RUTA IPC> por la ruta donde la cartera Ethereum busca el nodo de la red:
    • ~/Library/Ethereum/geth.ipc en Mac.
    • ~/.ethereum/geth.ipc en Linux.
    • ~/AppData/Roaming/Ethereum en Windows.

    La terminal quedará bloqueada y mostrando el log de lo que va sucediendo.

3.3. La consola JavaScript de Ethereum

  1. Desde otra terminal conectarse a la red con la consola JavaScript sustituyendo <RUTA IPC> como en el paso anterior:

  2. Ahora podemos comprobar que efectivamente hemos creado dos cuentas, la primera de ellas con algo de Ether:

  3. Dejar la terminal abierta para controlar el proceso de minado que veremos más adelante en este tutorial.
    Después de eso se podrá cerrar la consola con:

4. Los contratos

4.1. La interfaz ERC20

Aunque no es obligatorio, es muy recomendable que los contratos cumplan la interfaz ERC20, que permite a un contrato interactuar con la cartera oficial de Ethereum y cualquier otro cliente compatible.

Esta interfaz define los siguientes métodos y callbacks:

Las tres primeras funciones son opcionales; el resto es obligatorio implementarlas.

Como se puede ver a simple vista, la interfaz posibilita el intercambio de tokens del contrato usando cualquier herramienta compatible.
Obviamente un contrato tendrá otras funciones y eventos para ejecutar sus políticas y forzar sus condiciones.

4.2. Tu primer contrato

Puedes usar cualquier editor de texto para programar en Solidity.
Yo estoy usando Visual Studio Code con la extensión Solidity de @juanfranblanco.

Un ejemplo de contrato mínimo sería el siguiente fichero contrato-minimo.sol:

Este es el significado de cada parte:

  • pragma solidity ^0.4.18;
    Especifica la versión de Solidity.
  • contract MyToken { … }
    Declaración del contrato llamado MyToken.
  • mapping (address => uint256) public balanceOf;
    Los contratos son un tipo de cuentas en Ethereum y por tanto tienen su propio almacenamiento persistente en forma de mapa clave -> valor.
    Ambos elementos son arrays de 32 bytes, aunque Solidity nos permite trabajar con tipos de más alto nivel.
    En este ejemplo estamos definiendo un mapa para guardar el balance de tokens de cada cuenta que haya participado en el contrato.
  • function MyToken(uint256 initialSupply) public { … }
    Este es el constructor del contrato y aquella cuenta EOA que lo invoque se anotará el saldo inicial de tokens dado en el constructor.
  • balanceOf[msg.sender] = initialSupply;
    Esta es la lógica del constructor que guarda en el mapa de balances el saldo inicial de tokens del creador del contrato.
  • function transfer(address _to, uint256 _value) public { … } Esta función implementa la lógica de transferir tokens desde una cuenta (el que invoca la función) a otra dada.
  • if (balanceOf[msg.sender] < _value) { revert(); }
    En los contratos hay que verificarlo todo antes de llevar a cabo ninguna acción irreversible.
    Aquí estamos comprobando que el emisor de la transferencia tiene fondos suficientes.
  • if (balanceOf[_to] + _value < balanceOf[_to]) { revert(); }
    Esta segunda comprobación evita fallos de overflow.
  • balanceOf[msg.sender] -= _value;
    Ahora se restan los tokens del balance de la cuenta origen.
  • balanceOf[_to] += _value;
    Y se suman al balance de la cuenta destino.

4.3. Instalación del contrato

La instalación manual del contrato en el blockchain de Ethereum es tediosa e implica los siguientes pasos:

  1. Compilar el contrato a bytecode EVM con el compilador solc que aún no hemos instalado en este tutorial.
    Esto generaría los binarios con el código en sí y el ABI para crear el contrato.
  2. Estimar el coste de creación del contrato (startGas) y el precio del gas (gasPrice).
    Más sobre esto en el apartado 5.1.
  3. Desde la consola JavaScript de Ethereum, instanciar la factoría del contrato usando el contenido del ABI.
  4. En la misma consola, ejecutar la transacción que instancia el contrato usando la factoría del paso anterior y el código compilado del contrato.
    En este punto es donde se especifica la cuenta creadora del contrato (o sea, la que ejecuta la transacción) y el saldo inicial.
    Esta función es asíncrona porque las transacciones las ejecutan los mineros cuando aceptan el encargo.
    Cuando la transacción se completa se recupera la dirección de la cuenta de la instancia del contrato.
  5. Arrancar el proceso de minado en el nodo para que complete la transacción de instalación del contrato.

En vez de esto, usaremos la cartera de Ethereum:

  1. En primer lugar, asegúrate de que el nodo privado sigue ejecutándose en una terminal.
    Si no es así, vuelve al último punto del apartado 3.2.
  2. Abre la Ethereum Wallet y comprueba que se conecta al nodo de la red privada ( PRIVATE-NET):

    También deberían aparecer las dos cuentas EOA, una con saldo y la otra vacía.
  3. Ve a la pestaña CONTRATOS y selecciona la opción DEPLOY NEW CONTRACT.
    La cuenta de origen ya debería estar preseleccionada y la cantidad de ether 0, puesto que no vamos a realizar ninguna transferencia.
  4. Copia y pega el código fuente del contrato en la caja de texto SOLIDITY CONTRACT SOURCE CODE y selecciona My Token en el desplegable SELECT CONTRACT TO DEPLOY.
    Introducir también la cantidad de tokens iniciales que vas a repartir.
  5. A continuación, elige una tarifa (gasPrice) según las prisas que tengas y pulsa el botón DESPLEGAR.
  6. Entonces se nos presentará una ventana para verificar que todo es correcto, incluyendo el coste estimado de la transacción (startGas).
    Debemos introducir el password de la cuenta de origen (elegido en el apartado 3.2) y aceptar con SEND TRANSACTION.
  7. Si volvemos a la pestaña BILLETERAS veremos en la parte inferior una transacción pendiente de ejecutar:
  8. Ahora es el momento de volver a la consola JavaScript de Ethereum para minar ether durante el tiempo suficiente como para que se instale el contrato:

  9. Y ya deberíamos tener nuestro contrato listo para ser ejecutado:

4.4. Ejecución del contrato

Nuestro contrato solamente sirve para transferir cantidades de My Token entre cuentas.
Para poder realizar este tipo de transacción, primero hay que añadir My Token a la cartera de Ethereum:

  1. Ir a la pestaña CONTRATOS, seleccionar el contrato MY TOKEN, pulsar Copy address y confirmar que sabemos que esta cuenta está en una red de pruebas.
  2. Volver a los CONTRATOS y en CUSTOM TOKENS pulsar OBSERVAR FICHA.
    Pegar la dirección del contrato, asignar el nombre MY TOKEN y el símbolo MTK.
    Pulsar ACEPTAR para añadir el token a la cartera.

Ahora ya estamos listos para ejecutar la transferencia desde la propia cartera de Ethereum:

  1. En la pestaña BILLETERAS seleccionar la cuenta de destino (con saldo 0), pulsar el botón Copy address y confirmar la copia.
  2. En la pestaña ENVIAR, asegurar que la cuenta origen es la MAIN ACCOUNT y pegar en el campo TO la dirección copiada en el paso anterior.
  3. Seleccionar MY TOKEN en la lista de monedas e introducir una cantidad.
    Elegir también el gasPrice según nos interese esperar más o menos y confirmar el envío con el botón ENVIAR:
  4. En la siguiente pantalla veremos los datos de la transacción incluyendo una estimación del gas.
    Introducir la contraseña de la cuenta origen y pulsar SEND TRANSACTION para confirmar la transferencia.
  5. Igual que pasaba al crear el contrato, al final de la pestaña BILLETERAS aparecerá una transacción de tipo Contract Execution que está pendiente de ejecutar, así que hay que volver a minar durante unos segundos en la consola:

  6. Si ahora volvemos a BILLETERAS y seleccionamos la segunda cuenta veremos que, aunque el balance de ether sigue siendo 0, ahora al menos tiene la cantidad transferida del token MTK.
    También puede que te hayas fijado en que la cuenta principal ha aumentado su saldo de ether.
    Esto se debe a que esta es la cuenta a la que van a parar los beneficios del minado que hemos estado realizando.
  7. Para terminar, podemos cerrar la cartera Ethereum, la consola donde hemos controlado el minado y la terminal donde estaba corriendo el nodo de la red privada. El orden es importante.

5. El más allá

5.1. Coste de las transacciones

Las transacciones en Ethereum se ejecutan en los nodos de los mineros.
Para compensar la cesión de recursos hardware, cada transacción tiene un coste en gas que el emisor debe fijar a través de los parámetros startGas y gasPrice.

El primero se estima fácilmente simulando la ejecución de la transacción desde la consola JavaScript de Ethereum:

Aquí más que en ningún otro lugar es imprescindible optimizar al máximo la lógica del contrato para no desperdiciar gas.

Otro aspecto crítico es que las operaciones sobre datos tengan un tiempo de ejecución constante independientemente del tamaño de los datos.
Por ejemplo, una operación de búsqueda en una lista tiene un coste de ejecución O(n).
Si la búsqueda se basa en una clave podemos usar un mapa, teniendo entonces un tiempo de ejecución O(1).

Por otro lado, el gasPrice debe ser lo más pequeño posible que nos garantice que algún minero va a procesar nuestra transacción.
Según la prisa que tengamos pagaremos más o menos, aunque la comunidad de desarrolladores siempre intenta tirar por lo bajo para evitar que los precios se disparen.
La mejor referencia del estado actual del gas está en: https://ethgasstation.info/

5.2. Contratos complejos

El desarrollo de contratos inteligentes no difiere mucho del desarrollo de cualquier otro tipo de software.
Como tal debemos enfrentarnos a él siguiendo los mismos principios generales de análisis y diseño de software, y en particular los principios SOLID.

Para ayudarnos en esta tarea, nuestra mejor opción es el framework truffle.
Definitivamente es la base para cualquier desarrollo serio.

5.3. Seguridad y robustez

La seguridad y la robustez son aspectos críticos cuando estamos hablando de software que maneja dinero.
De hecho, el mayor robo de ether se consiguió a través de un agujero de seguridad en un contrato DAO, hecho que dio lugar al fork de Ethereum Classic.
Para reducir al mínimo la probabilidad de fallos y agujeros debemos hacer dos cosas: seguir las mejores prácticas de desarrollo y testear la funcionalidad del contrato.

Las mejores prácticas de diseño y desarrollo están documentadas en la web Ethereum Smart Contract Best Practices.
Síguelas al pie de la letra.

Por otro lado, el testing es uno de los aspectos a mejorar en la plataforma Ethereum.
Otros blockchain más modernos como Cardano han nacido incorporando de serie la idea de la verificación automática de contratos.
Sin entrar en detalles, el testing en Ethereum se hace a través de truffle, que por debajo utiliza Mocha.

5.4. Cómo usar el contrato

Cuando nuestro contrato ya está desplegado e instanciado en la red Ethereum ¿cómo se supone que lo utilizan los usuarios?
Principalmente hay dos caminos para que los usuarios ejecuten las operaciones del contrato:

  • A través de alguno de los hub de aplicaciones descentralizadas, en concreto: https://www.stateofthedapps.com/
  • Con una aplicación móvil o web que se conecte a un nodo de la red Ethereum con una librería como web3j o directamente con llamadas remotas JSON-RPC.

6. Conclusiones

La situación actual de especulación en el mercado de las criptomonedas ha provocado un boom de aplicaciones descentralizadas basadas en contratos inteligentes, la mayoría de ellas humo con poca o ninguna aportación de valor real.

Esto no quita para que la tecnología de blockchain de contratos inteligentes tenga sus casos de uso prácticos y útiles.
Simplemente hay que tener en cuenta los beneficios que nos aporta el concepto de registro público inalterable y verificado basado en el consenso y sin autoridad central.
Tampoco deben olvidarse los perjuicios del blockchain. El más importante en mi opinión es el enorme gasto energético necesario para realizar cualquier transacción, lo que limita la complejidad de los procesos que estamos dispuestos a automatizar.

En definitiva, se trata de una tecnología bastante nueva, con mucho recorrido y con buenas ideas por descubrir. Quizás la próxima sea la tuya…

7. Referencias