ethereum development smartcontracts

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

Volvemos a la carga con la miniserie de  desarrollo en blockchain 2.0 y que en su capitulo anterior Desarrollo en blockchain 2.0: Creando nuestro primer Smart Contract (I) ya dejamos todo listo para empezar a codificar nuestro primer smart contract 100% nuestro.

Si no habéis leído al menos el post anterior de la serie, es recomendable que lo hagáis ahora puesto que, en aras de la brevedad, vamos a dar por supuesto que tenéis vuestro puesto de desarrollo configurado, y que tenéis unas nociones básicas sobre Truffle, testrpc y Solidity (por no mencionar NodeJS).

Nos situamos en el directorio del proyecto y mas concretamente en el directorio contracts donde vamos a crear un nuevo fichero HelloWorld.sol con nuestro editor favorito (de nuevo, en mi caso, Visual Studio Code). Una vez creado el fichero HelloWorld.sol, vamos a introducir el siguiente código fuente dentro de el (recordad que en Ethereum una de las opciones para desarrollar “smart contracts” es el lenguaje Solidity que se parece mucho a Javascript.

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
pragma solidity ^0.4.4;
 
import "./StringUtils.sol";
 
contract MyHelloWorld {
   using StringUtils for *;
 
   // We store the messages sent to every address
   mapping (address => string ) _messages;
 
   // Represents the owner of the contract instance
   address _owner;
 
   // Constructor
   function MyHelloWorld () {
      // Initializes the owner of the contract instance
      _owner = tx.origin;
   }
 
   // Getter of the 'owner' property
   function getOwner () returns (address owner) {
      return _owner;
   }
 
   // Sends a message to an specific address. Only available
   // for the owner of the contract instance
   function sendMessage (address receiver, string m) {
      // Only the contract instance owner can send messages
      if (msg.sender != _owner) throw;
   _   messages[receiver] = m;
   }
 
   // Returns the message (if any) for the method caller
   function getMessage () returns (string m) {
      if (_messages[msg.sender].toSlice().len() > 0) return _messages[msg.sender];
   }
}

Vamos a analizar brevemente algunas partes de nuestro código fuente para que podamos entender que estamos desarrollando.

1
pragma solidity ^0.4.4;

En esta linea estamos pasandole a nuestra maquina virtual de ethereum (EVM) que una directiva de compilación indicándole cual es la minima version de Solidity que debería soportar para poder compilar nuestro smart contract (como curiosidad os dire que a fecha de redacción de este articulo, la ultima version de Solidity es la 0.4.11, pero lamentablemente nuestro testrpc no soporta versiones superiores a la 0.4.4 de momento).

3
import "./StringUtils.sol";

En esta linea le indicamos al compilador que importe un fichero (StringUtils.sol) que contiene un conjunto de funciones de manipulación de strings que utilizaremos en los métodos de nuestro smartcontract. Podéis descargaros el fichero de este repositorio de GitHub https://github.com/Arachnid/solidity-stringutils/blob/master/strings.sol (gracias a su autor Arachnid por facilitar el código con licencia Apache 2.0) y almacenarlo también dentro del directorio contracts del proyecto con el nombre StringUtils.sol

5
6
contract MyHelloWorld {
   using StringUtils for *;

Estas dos lineas declaran un smart contract llamado MyHelloWorld y dentro de él indicamos que vamos a hacer uso de todas las funciones definidas en la librería que hemos comentado en el párrafo anterior.

A continuación, y de manera muy similar a la definición de clases de cualquier lenguaje orientado a objetos vamos a definir las variables miembros de nuestro smart contract y también su constructor por defecto y algunos métodos o funciones miembro del smart contract.

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
   // We store the messages sent to every address
   mapping (address => string ) _messages;
 
   // Represents the owner of the contract instance
   address _owner;
 
   // Constructor
   function MyHelloWorld () {
      // Initializes the owner of the contract instance
      _owner = tx.origin;
   }
 
   // Getter of the 'owner' property
   function getOwner () returns (address owner) {
      return _owner;
   }
 
   // Sends a message to an specific address. Only available
   // for the owner of the contract instance
   function sendMessage (address receiver, string m) {
      // Only the contract instance owner can send messages
      if (msg.sender != _owner) throw;
   _   messages[receiver] = m;
   }
 
   // Returns the message (if any) for the method caller
   function getMessage () returns (string m) {
      if (_messages[msg.sender].toSlice().len() > 0) return _messages[msg.sender];
   }
}

Como veis el Smart Contract implementa una lógica muy sencilla que permite a un usuario el almacenar mensajes para otros usuarios (direcciones de blockchain) y permite a los destinatarios el consultar los mensajes que les correspondan y no otros.

Como parte interesante podéis ver que la tecnología de blockchain garantizaría la integridad de los mensajes (solo el owner del contrato puede darlos de alta y solo los destinatarios podrán consultar sus mensajes sin poder modificar los que no sean suyos). Este escenario, un poco mas elaborado os lo vais a encontrar mucho en blockchain (registro de identidades, mensajes certificados, etc.)

Tenemos pues nuestro primer smart contract. Vamos a crear algunos tests unitarios para comprobar su funcionamiento (y de paso tener un proceso de integración continua en nuestro proyecto).

Creamos un fichero nuevo test/helloworld.js en el directorio test del proyecto. Incluimos el siguiente código dentro de el:

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
var HelloWorld = artifacts.require("./MyHelloWorld.sol");
 
contract('MyHelloworld', function (accounts) {
    it("Should be able to create a contract and send a message", function () {
        var expectedMessage = "Hello Account #1!"
        var myhw;
        return HelloWorld.deployed().then(function (instance) {
            myhw = instance;
            return myhw.sendMessage(accounts[1], expectedMessage);
        }).then(function () {
            return myhw.getMessage.call({ from: accounts[1] });
        }).then(function (retval) {
            assert.equal(retval, expectedMessage, "The returned message was not equal to expected one!");
        });
    });
 
    it("Should be able to get the owner of a contract", function () {
        return HelloWorld.deployed()
            .then(function (instance) {
                return instance.getOwner.call();
            })
            .then(function (owner) {
                assert.equal(accounts[0], owner, "The owner was not the same as expected")
            })
    });
 
    it("Should allow the creation of a new instance of a contract", function () {
        return HelloWorld.new({ from: accounts[1] }).then(function (instance) {
            return instance.getOwner.call().then(function (owner) {
                assert.equal(owner, accounts[1], "Incorrect expected owner");
            });
        });
    });
});

Como veréis (y ya anticipamos en el articulo anterior de la serie) Truffle utiliza MochaPromises para realizar los tests unitarios de los smart contracts del proyecto. En nuestro anterior fichero hemos creado 3 pruebas unitarias sobre nuestro smart contract MyHelloWorld. Si queremos invocar las pruebas unitarias solo hemos de teclear lo siguiente en nuestro terminal, dentro de nuestro directorio del proyecto:

$> truffle test

Truffle delegará a Mocha la ejecución de todos los tests unitarios que encuentre dentro del directorio test del proyecto. Si quisiéramos solamente ejecutar uno de los ficheros de test unitarios lo añadiríamos como parámetro a la anterior línea de comando:

$> truffle test test/helloworld.js

Si todo va bien, Mocha nos presentará un informe de ejecución de las pruebas unitarias.

OBSERVACIÓN: Acordaos de tener en ejecución testrpc para que Truffle pueda invocar la ejecución de las pruebas unitarias contra dicha instancia de blockchain (para ello, lo mas sencillo es abrir un terminal aparte y ejecutar testrpc simplemente)

helloworld blockchain truffle test exec

Como veréis el contrato MyHelloWorld ha ejecutado sus tres tests unitarios sin ningún problema.

También como habréis visto tenemos varias formar de desarrollar nuestros tests unitarios. La primera forma es con un fichero javascript que hace uso de algunas construcciones que Truffle genera (objeto artifacts o la función contract) para que desde Mocha podamos invocar a nuestro testrpc así como a nuestro smart contract. La segunda forma es codificar nuestros test unitarios utilizando Solidity (tenéis un ejemplo en el fichero TestMetacoin.sol). Ambas formas son igualmente validas (personalmente aunque suponga escribir mas LOCs, prefiero usar Mocha directamente) y serán igualmente invocadas por Truffle.

Con esto ya tenemos una idea aproximada y ligera de como empezar a desarrollar nuestros smart contracts así como sus pruebas unitarias. En un siguiente articulo dentro de esta serie, avanzaremos y empezaremos a generar un dApp que haga uso de nuestros smart contracts.

Leave a Reply

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