Desarrollo en blockchain 2.0: Creando nuestro primer Smart Contract (I)

En este nuevo “post” continuamos con la miniserie de posts sobre Desarrollo en blockchain 2.0 que inauguramos con el “post” anterior sobre Desarrollo en blockchain 2.0: Configurando nuestro puesto de desarrollo en el que preparamos nuestro puesto de desarrollo con el software necesario para empezar a desarrollar con Ethereum.

Si recordáis del “post” anterior, vamos a desarrollar con Node.JS y usaremos Truffle como framework para desarrollo en Ethereum y TestRPC para disponer de nuestro propio blockchain en memoria.

Una vez que tenemos todas nuestras dependencias instaladas, empezamos por crear un directorio en nuestro puesto de desarrollo (yo os dejo como lo he hecho yo, pero seguramente vosotros podréis organizar el proyecto de manera sencilla a vuestro antojo):

$ mkdir -p ~/Projects/Blockchain/hello-world-truffle

$ cd ~/Projects/Blockchain/hello-world-truffle

Ahora una vez que estamos dentro del directorio que sera nuestro primer proyecto con blockchain empezamos por indicarle a Truffle que nos genere el esqueleto del proyecto (“scaffolding” o andamiaje en castellano):

$ truffle init

git

Como vemos, Truffle nos inicializa el proyecto creando una serie de directorios y algunos ficheros de configuración.

En este momento es muy recomendable que empecemos a gestionar el código fuente del proyecto con Git para poder tener versionado e incluso una copia remota del código fuente por si nuestra VM se quema. Para ello dentro del directorio del proyecto le indicamos a Git que nos cree un repositorio:

$ git init

Una vez creado el repositorio para el proyecto, le indicamos a Git que almacene la primera version (o primer “changeset”) de nuestro proyecto:

$ git add .

$ git commit -m “Version inicial”

Ahora podemos seguir jugando con el codigo con la seguridad de contar con una red de seguridad que nos permitirá volver atrás en caso de cometer algún error.

Ahora, podemos abrir nuestro editor preferido (recordad que en mi caso es Visual Studio Code) y empezaremos a echar un vistazo al contenido del proyecto abriendo desde el el directorio con la opción “File>Open Folder…”

Veamos que nos ha creado Truffle al inicializar el proyecto.

Primero de todo encontramos 3 directorios:

  • contracts: aquí estará el código fuente de nuestros smartcontracts. De hecho, Truffle nos ha generado 2 smartcontracts (MetaCoin.sol y Migrations.sol) y una librería “smart” (ConvertLib.sol)
  • migrations: En este directorio Truffle busca los scripts que utilizara para desplegar nuestros smartcontracts a su posterior entorno de ejecución. Los scrips de despliegue están priorizados (fijaos en el prefijo numérico de los archivos que hay dentro) y se ejecutaran en dicho orden.
  • test: Como buen “framework” de desarrollo, Truffle nos ha generado una serie tests unitarios para que podamos tener integración continua (CI) en nuestro proyecto. Para ello, Truffle utilizara el paquete Mocha de pruebas unitarias en Node.JS. En este directorio dejaremos nuestros ficheros de pruebas unitarias.

Tenemos ya un proyecto creado (aunque no hayamos contribuido mucho todavía) así que vamos a ejecutarlo a ver que hace ¿no? Si recordamos, vamos a utiliza TestRPC en nuestro puesto de desarrollo para tener una red de Ethereum con prácticamente toda su funcionalidad ejecutando en nuestra maquina y sobre esta red de blockchain vamos a desplegar (migrar, según la terminología de Truffle) nuestros smartcontracts. Así que vamos a abrir una segunda ventana de terminal, y sobre ella ejecutaremos TestRPC.

$ testrpc

Es importante que no cerremos esta ventana de terminal puesto que si no nuestra red de Ethereum junto con su estado desaparecerá con ella (recordemos que TestRPC se ejecuta en memoria y persiste en memoria, no deja nada en el disco de nuestra maquina). Al cabo de unos segundos tendremos una instancia de Ethereum ejecutando en nuestro puesto de desarrollo con un conjunto por defecto de cuentas (“accounts”) como podéis ver en el pantallazo a continuación.

helloworld blockchain testrpc startup

Otra ventaja del tandem Truffle/TestRPC es que ya se conocen “de serie”. Si nos fijamos en el fichero truffle.js ya viene configurado para que nuestros despliegues de desarrollo vayan directamente contra localhost y el puerto por defecto de TestRPC: 8545. Esto quiere decir que por defecto cualquier acción que desencadenemos con Truffle ira contra nuestra instancia de TestRPC sin que tengamos que tocar nada de su configuración.

Dicho esto, primero de todo vamos a compilar y a desplegar los contratos que nos ha creado Truffle como esqueleto de nuestro proyecto. Invocamos a Truffle y le indicamos que queremos realizar la migración.

$ truffle migrate

helloworld blockchain truffle migrate

Como vemos, Truffle ha “compilado” todos los ficheros .sol del directorio test y luego ha ejecutado los scripts del directorio migrations en el orden en que determinan sus nombres (1_initial_migration.js2_deploy_contracts.js)

Hasta aquí solo hemos visto que el proyecto esta bien generado pero no hemos visto ningún smartcontract en ejecución. Para ello vamos a pedirle a Truffle que ejecute los test unitarios que ha generado durante el proceso de “scaffolding” inicial.

$ truffle test

helloworld blockchain truffle test

Si miramos lo que el comando ha provocado veremos que aparte de compilar de nuevo los smartcontracts también ha utilizado Mocha para ejecutar los test unitarios dentro del directorio test y que (como no podría ser de otra forma) los test unitarios han funcionado todos correctamente. Echemos un vistazo a esos test unitarios. Nos vamos a Visual Studio Code y abrimos el fichero metacoin.js

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
var MetaCoin = artifacts.require("./MetaCoin.sol");
 
contract('MetaCoin', function(accounts) {
  it("should put 10000 MetaCoin in the first account", function() {
    return MetaCoin.deployed().then(function(instance) {
      return instance.getBalance.call(accounts[0]);
    }).then(function(balance) {
      assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");
    });
  });
  it("should call a function that depends on a linked library", function() {
    var meta;
    var metaCoinBalance;
    var metaCoinEthBalance;
 
    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(accounts[0]);
    }).then(function(outCoinBalance) {
      metaCoinBalance = outCoinBalance.toNumber();
      return meta.getBalanceInEth.call(accounts[0]);
    }).then(function(outCoinBalanceEth) {
      metaCoinEthBalance = outCoinBalanceEth.toNumber();
    }).then(function() {
      assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");
    });
  });
  it("should send coin correctly", function() {
    var meta;
 
    // Get initial balances of first and second account.
    var account_one = accounts[0];
    var account_two = accounts[1];
 
    var account_one_starting_balance;
    var account_two_starting_balance;
    var account_one_ending_balance;
    var account_two_ending_balance;
 
    var amount = 10;
 
    return MetaCoin.deployed().then(function(instance) {
      meta = instance;
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_starting_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_starting_balance = balance.toNumber();
      return meta.sendCoin(account_two, amount, {from: account_one});
    }).then(function() {
      return meta.getBalance.call(account_one);
    }).then(function(balance) {
      account_one_ending_balance = balance.toNumber();
      return meta.getBalance.call(account_two);
    }).then(function(balance) {
      account_two_ending_balance = balance.toNumber();
 
      assert.equal(account_one_ending_balance, account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");
      assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");
    });
  });
});

Como podemos ver es un típico test unitario de Mocha con una particularidad y es que en la primera linea le pedimos al test que cargue los artefactos generados de la compilación del smartcontract definido en el fichero MetaCoin.sol. Con todo esto tenemos ya los instrumentos para poder empezar a desarrollar y a probar nuestro primer smartcontract.

No os perdáis el siguiente “post” de la serie donde empezaremos con esa tarea.

Leave a Reply

Your email address will not be published. Required fields are marked *