Meetup RSK – Conceptos básicos de Solidity y Truffle

¡Hola a todos!

El pasado lunes 9 de abril estuvimos presentando en el Meetup ¿Eres dev y aún te rompe la cabeza eso de Blockchain? para dar una breve introducción junto con Alex Casas sobre la tecnología blockchain y la plataforma P2P sobre Bitcoin que permite la ejecución de contratos inteligentes, RSK.

La primera parte del Meetup la podéis encontrar en nuestro blog aquí.

Como en el post anterior tambien queremos dejar constancia del proceso de desarrollo de contratos con Solidity usando el framework Truffle. ¡Así que vamos a ello!

Para seguir esta guía el único requisito es tener instalado NodeJS v8.x LTS. Pero además recomiendo que instales como IDE el Visual Studio Code por su fácil manejo y la posibilidad de poder tener la consola de comandos en la misma ventana.

Instalar Truffle:

¡Vamos a ello! Lo primero que debemos hacer es instalar truffle usando el gestor de paquetes de Node. Lo hacemos de la siguiente forma:

$ npm install -g truffle

Una vez acabada la instalación vamos a crear una carpeta en nuestra carpeta personal para iniciar un proyecto de Truffle:

$ mkdir ~/smartContracts
$ cd ~/smartContracts
$ truffle init

Si listamos los ficheros y carpetas de nuestra carpeta personal veremos que truffle nos ha creado el esqueleto de lo que será nuestro proyecto para el Smart Contract. Veremos las siguientes carpetas y ficheros:

  • contracts: aquí se almacenarán los contratos que creemos
  • migrations: aquí estarán los scripts que se ejecutarán al hacer migrate
  • test: aquí almacenaremos los test para nuestros contratos (en futuras guías)
  • truffle-config.js: fichero de configuración de truffle para Windows
  • truffle.js: fichero de configuración de truffle para todos los sistemas menos Windows

Primer Ejemplo:

Con nuestro proyecto truffle iniciado vamos a proceder a crear nuestro primer contrato. Para crear un comando tenemos dos opciones. Podemos hacerlo creando un fichero .sol dentro de nuestra carpeta contracts o podemos ejecutar la siguiente línea en nuestra consola de comandos situada en la carpeta raíz del proyecto:

$ truffle create contract HolaMundo

Recomiendo hacerlo al principio por consola porque veremos que el contrato que nos crea ya tiene algo de contenido:

pragma solidity ^0.4.4;

contract HolaMundo {
function HolaMundo() {
// constructor
}
}

Cosas que nos interesa comentar:

  • La palabra reservada pragma seguida de solidity y su versión indican al compilador que versión de solidity tiene que compilar
  • Se podría decir que crear un contrato sería similar a crear una clase en POO
  • Los contratos tienen constructores que se ejecutan al desplegar el contrato en la red

Teniendo este contrato escrito vamos ahora a compilarlo con los siguientes comandos (la consola siempre apuntando a la carpeta raíz del proyecto):

$ truffle compile

El outuput que nos dé ese comando debe ser similar a:

Compiling ./contracts/HolaMundo.sol...
Compiling ./contracts/Migrations.sol...

Compilation warnings encountered:

./contracts/HolaMundo.sol:5:3: Warning: No visibility specified. Defaulting to "public".
function HolaMundo() {
^ (Relevant source part starts here and spans across multiple lines).

Writing artifacts to ./build/contracts

Por ahora vamos a obviar los Warnings. Si el output ha sido el que os he mostrado veremos que truffle nos ha creado en la raíz del proyecto una carpeta llamada build. Aquí se guardan los fichero que genera la compilación de nuestros contratos. Veremos que hay dos que se corresponden a nuestros dos contratos en la carpeta contracts: Migrations y HolaMundo. Estos ficheros contienen toda la información que necesita truffle para desplegarlos en la blockchain.

Para acabar de preparar el despliegue nos falta crear un script en la carpeta migrations donde le vamos a indicar a truffle que es lo que tiene que desplegar. Vamos a crear un fichero con el nombre 2_deploy_contracts.js dentro de la carpeta migrations. En este script le vamos a indicar que contratos queremos desplegar, en este caso solo es uno pero seguro que nos es muy útil para un futuro próximo. Escribimos lo siguiente en el fichero que hemos creado:

var HolaMundo = artifacts.require("./HolaMundo.sol");

module.exports = function(deployer) {
deployer.deploy(HolaMundo);
};

En resumen lo que hacemos en este script es importar el contrato y luego decirle al desplegador (deployer) que ese es el contrato que queremos desplegar. Truffle a partir del nombre ya sabe donde ir a buscar el fichero compilado.

Vamos ahora a desplegarlo en la blockchain de test que nos ofrece truffle. Escribimos en la consola:

$ truffle develop

Y el output debería ser:

Truffle Develop started at http://127.0.0.1:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

Important : This mnemonic was created for you by Truffle. It is not secure.
Ensure you do not use it on production blockchains, or else you risk losing funds.

truffle(develop)>

De este output podemos entender dos cosas:

  • Que nos ha levantado una blockchain local que podremos llamar desde el puerto 9545
  • Nos ha creado 10 cuentas de las que nos da sus claves públicas y las privadas

Llegados a este punto ya solo queda dar la instrucción para que despliegue el contrato. Ejecutamos en la consola de truffle lo siguiente:

truffle(develop)> migrate

El output será:

Using network 'develop'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xed37c5bf226434927601965c764645d8f495702cb02d26ec71bdb03687eb3ac9
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying HolaMundo...
  ... 0x99eb85b6d8de4238c109f0b21d5e2de8b76bff03969bdd4c73cb12b33167ff3b
  HolaMundo: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

Donde podremos ver que ha usado los ficheros .js que se encuentran en la carpeta migrations. Además podemos ver que migrado los contratos Migrations y HolaMundo.

¡Genial! ¡Hemos desplegado nuestro primer contrato en una blockchain! ¿Vamos ahora a meterle algo mas de lógica al contrato, no?

Actualmente nuestro contrato no hace absolutamente nada, así que vamos a hacer que por lo menos nos devuelva un saludo cuando lo llamemos. Para esto vamos a ponerle una variable de estado que se cargará cuando se llame al constructor. Lo que sería el siguiente código:

pragma solidity ^0.4.19;

contract HolaMundo {
  string public content;

  function HolaMundo() {
    content = "Hola Mundo!";
  }
}

Nótese dos cosas:

  • He cambiado la versión del compilador a la 0.4.19 para estar mas actualizados con la última versión
  • He declarado la variable de estado content como pública

Una vez hechos los cambios en el código del contrato sin salir de la consola de desarrollo de Solidity vamos a ejecutar lo siguiente:

truffle(develop)> compile
truffle(develop)> migrate --reset

En esta ocasión hemos utilizado el parámetro –reset al hacer el migrate porque como hemos seguido con la misma instancia de la red blockchain y ya habíamos cargado un contrato con el nombre de HolaMundo había que indicarle a truffle que queríamos vacíar esa instancia del contrato para desplegar la nueva versión.

Aclarado eso vamos a llamar a la variable del contrato para ver que contiene. Recordad que Truffle es un framework de JS con lo que las instrucciones en la consola van a ser JS. Vamos a ello:

truffle(develop)> var contract = HolaMundo.at(HolaMundo.address)
truffle(develop)> contract.content()

Aquí hemos volcado el contrato en nuestra variable contract. Al tener la instacia del contrato en nuestra variable podemos acceder a sus métodos y variables públicas. Hay que darse cuenta que al llamar a la variable content hemos indicado los parentesis como una función. Esto se debe a que cuando declaras una variable pública el compilador de Solidity automáticamente te genera una función para que puedas acceder a su contenido. Lo que sería igual a:

string public content;

function content() {
 return content;
}

Segundo Ejemplo:

¡Genial! ¡Ya tenemos un contrato que almacena datos en una variable! Ahora vamos a subir un puntito de nada la complejidad y vamos a permitir que el contenido de esa variable sea modificada desde fuera:

pragma solidity ^0.4.19;

contract HolaMundo {
  string public content;

  function HolaMundo() {
    content = "Hola Mundo!";
  }

  function setContent(string newContent) {
    content = newContent;
  }
}

Una vez este guardado hacemos lo mismo de antes para compilar, migrar y volcar la instancia del contrato sobre nuestra variable. Además vamos a llamar recuperar el contenido de la variable content para luego modificarla y volver a llamarla y verificar el cambio de contenido:

truffle(develop)> compile
truffle(develop)> migrate --reset
truffle(develop)> var contract = HolaMundo.at(HolaMundo.address)
truffle(develop)> contract.content()
'Hola Mundo!'
truffle(develop)> contract.setContent("Hola blog de Blocknitive");
{ tx: '0x0ee92701e51c510aa0858afe4d5a255cbf2af669ee9da9eaf40949113d1da90d',
  receipt:
   { transactionHash: '0x0ee92701e51c510aa0858afe4d5a255cbf2af669ee9da9eaf40949113d1da90d',
     transactionIndex: 0,
     blockHash: '0x9750a554cb431fec75ab2599b7c3f213cd7672e8554b1f5fbf984c6157270916',
     blockNumber: 13,
     gasUsed: 34248,
     cumulativeGasUsed: 34248,
     contractAddress: null,
     logs: [],
     status: '0x01',
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' },
  logs: [] }
truffle(develop)> contract.content()
'Hola blog de Blocknitive'

Veremos que al cambiar la variable de estado nos ha devuelvo un objeto JSON donde podemos ver el Gas usado en la operacion (el gas es lo que cuesta cambiar el estado en nuestros contratos en la blockchain). También vemos al volver a acceder a la variable que efectivamente nos ha cambiado su contendido.

Tercer Ejemplo:

¡Vamos ahora a por el tercer y último ejemplo!
En este ya que sabemos acceder y modificar el estado de variables vamos a usar el tipo de dato mapping para permitir que cada usuario que llame al contrato pueda almacenar el contenido que desee. Para eso vamos a modificar nuestro contrato:

pragma solidity ^0.4.19;

contract HolaMundo {

  mapping(address => string) public saludos;

  function setSaludo(string newContent) public {
    saludos[msg.sender] = newContent;
  }

  function getSaludo() public view returns (string) {
    return saludos[msg.sender];
  }
}

Resaltar que para saber quien está llamando al contrato usamos la propiedad msg.sender que es un dato que se envía en cada llamada. Nos permite saber la cuenta que está llamando al contrato.

Vamos ahora a volver a desplegar el contrato compilandolo antes y volcando luego la instancia en una variable. Y ahora vamos a empezar a almacenar nuestros datos desde las distintas cuentas que nos da Truffle para luego acceder a la variable desde cada una para ver su contenido:

truffle(develop)> contract.setSaludo("Hola cuenta por defecto!")
truffle(develop)> contract.setSaludo("Hola cuenta del indice 4", {from: web3.eth.accounts[4]})

Truffle como es un entorno de pruebas nos permite sobrescribir las propiedades del objeto msg que hemos comentado antes. De esta forma en la segunda linea podéis ver que utilizo el array de cuentas que nos ha creado al arrancar la consola para acceder a la cuenta del índice del array 4 y así reemplazar el sender al llamar el contrato. Podemos ver que efectivamente cada cuenta ha almacenado sus propio contenido en la variable de la siguiente manera.

truffle(develop)> contract.getSaludo()
'Hola cuenta por defecto!'
truffle(develop)> contract.getSaludo({from: web3.eth.accounts[4]})
'Hola cuenta del indice 4'

Efectivamente vemos que cada cuenta que ha llamado al contrato ha almacenado un saludo distinto.

Hasta aquí esta pequeña guía sobre desarrollo de Solidity con Truffle. Os dejo aquí abajo las páginas con la docu del lenguaje y de la herramienta:

DISCLAIMER: Esta pequeña guía pretende mostrar de forma simple un funcionamiento superficial de Truffle y el lenguaje Solidity. No mostrar la correcta lógica que habría que aplicar sobre un contrato inteligente. Cualquier sugerencia de mejora, duda o comentario estaremos encantados de leerlo en la caja de comentarios. ¡Gracias!

2 comentarios de “Meetup RSK – Conceptos básicos de Solidity y Truffle”

Deja un comentario

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