Blockchain: Preparar el entorno para el desarrollo en Ethereum en local

Ya has visto que el IDE Remix es el sitio perfecto para empezar a desarrollar smart contracts en la Blockchain de Ethereum. En él puedes programar, compilar e incluso desplegar en un sandbox tus desarrollos. Sin embargo, tiene sus limitaciones. La primera más importante es que una vez que refrescas el navegador todo desaparece, lo cual resulta bastante engorroso si todo va más allá de una simple prueba. Es por ello que hoy quiero que preparemos el entorno para el desarrollo en Ethereum en local.

Instalar el compilador de Solidity

Antes de comenzar, debes tener instaladas las herramientas de compilación de C y C++. En el caso de MacOS si ya tienes instalado XCode no es necesario que hagas nada más. En Linux puedes usar el paquete build-essential y en Windows del paquete npm windows-build-tools.

Para instalar el compilador de Solidity puedes utilizar cualquiera de estas opciones. En mi caso, estoy trabajando con MacOS, por lo que usaré Homebrew:

brew update
brew upgrade
brew tap ethereum/ethereum
brew install solidity

Puedes comprobar que la instalación ha sido satisfactoria comprobando la versión del compilador:

solc --version

Desarrollo en local de los smart contracts con Visual Studio Code

Ya te he hablado de Visual Studio Code en diferentes escenarios y lenguajes. También puedes utilizarlo para el desarrollo de los smart contracts de Ethereum. Además, te recomiendo que instales la extensión de Juan Blanco para Solidity, la cual te permitirá ver la sintaxis del lenguaje coloreada, además de ayudarte con la compilación.

Para no alargar mucho el artículo, vamos a utilizar el mismo ejemplo que vimos en el articulo anterior: TimeControl.sol.

pragma solidity ^0.5.8;

contract TimeControl{    
    
    address private owner;
    mapping (address => uint[]) private employeeRecords;
    
    constructor() public{        
        owner = msg.sender;
    }
    
   function Register() public{
       employeeRecords[msg.sender].push(now);
   }   
  
   function GetMyRegistries() public view returns (uint[] memory){
       
       uint[] memory result = new uint[] (employeeRecords[msg.sender].length);
       for (uint i = 0;i < employeeRecords[msg.sender].length; i++) {
           result[i] = employeeRecords[msg.sender][i];    
       }
        
        return result;
    }    
}

Si pulsas F5 desde Visual Studio Code, podrás compilar el contrato de una manera muy sencilla. También puedes usar F1 y buscar la opción Solidity: Compile Current Solidity Contract.

Por último, también puedes utilizar el propio terminal y ejecutar solc con los siguiente comandos.

solc TimeControl.sol --bin --abi --optimize -o bin

Verás que la compilación ha sido correcta si obtienes el siguiente mensaje:

Compiler run successful. Artifact(s) can be found in directory bin.

Cualquiera de las tres opciones generarán dos archivos: TimeControl.bin y Timecontrol.abi. El archivo .bin es el bytecode que las EVM (Ethereum Virtual Machine) instaladas en los nodos de una red de Ethereum son capaces de ejecutar. El archivo .abi es la Application Binary Interface (ABI), que describe el contrato en formato JSON (como el Swagger o OpenAPI de las APIs). Este archivo se utiliza en el lado del cliente para poder llamar a nuestro contrato ya desplegado en una red Ethereum.

Para finalizar esta sección, te conté en el artículo anterior que las operaciones en Ethereum tienen un coste, dependiendo de lo fácil o difícil que sea llevarlas a cabo. Me parece interesante que veas una de las utilidades que tiene el compilador, que es ver cuál es el coste de cada una de las acciones del contrato:

solc TimeControl.sol --gas
Estimación de gas de TimeControl.sol proporcionada por solc

Compilar smart contracts con el módulos solc de Node.js

Otra opción que tenemos para compilar nuestro contracto inteligente es a través del módulo de solc en Node.js. Para ello, necesitamos instalarlo en nuestro proyecto, lo cual será más portable que instalarlo solc en la máquina local:

npm install solc --save-dev

El objetivo es el mismo: recuperar la ABI y el bytecode del contrato. Podemos utilizar el siguiente código para lograrlo:

const path = require('path');
const fs = require('fs');
const solc = require('solc');

const contractPath = path.resolve(__dirname, "..", 'TimeControl.sol');
const source = fs.readFileSync(contractPath, 'utf8');

let jsonContractSource = JSON.stringify({
    language: 'Solidity',
    sources: {
        'TimeControl': {
            content: source,
        },
    },
    settings: {
        outputSelection: {
            '*': {
                '*': ['abi', "evm.bytecode"]
            },
        },
    },
});

let solcResult = solc.compile(jsonContractSource);
const abi = JSON.parse(solcResult).contracts.TimeControl.TimeControl.abi;
const bytecode = JSON.parse(solcResult).contracts.TimeControl.TimeControl.evm.bytecode.object;

Como ves, a parte de recuperar el código fuente del archivo TimeControl.sol, generamos un JSON con todas las propiedades que necesitamos para la compilación. Utilizamos solc.compile para compilar y guardamos el ABI y el bytecode del JSON resultante.

Ahora lo único que nos queda es tener una red Ethereum, donde desplegar este contrato e interactuar con él.

Ganache: la red de pruebas de Ethereum local

Ganache es un software que nos proporciona una red de pruebas local súper sencilla e intuitiva. Para instalarlo, solo tienes que ir a https://truffleframework.com/ganache y descargar el ejecutable para su instalación.

Página de Truffle Framework – Ganache

Acepta (o no) el envío de telemetría para mejorar el software y haz clic en la opción QuickStart y ya está: una red Ethereum disponible en tu máquina.

¿Fácil verdad? Por último, lo único que nos queda es desplegar nuestro contrato en Ganache e interactuar con él.

Desplegar el contrato en Ganache con web3

Para esta tarea, vamos a utilizar la librería web3.js, a continuación del código que escribiste para compilar nuestro smart contract.

const Web3 = require('web3');

let web3 = new Web3(new Web3.providers.HttpProvider("HTTP://127.0.0.1:7545"));

web3.eth.getAccounts().then(accounts => {

    //Get the account which create the contract
    let creatorAccount = accounts[0];

    //Deploy contract
    const contract = new web3.eth.Contract(abi);

    contract.deploy({
        data: '0x' + bytecode

    }).send({
        from: creatorAccount,
        gas: 1500000,
        gasPrice: '30000000000000'
    }, (error, transactionHash) => {

        if(error){
            console.log(`error: ${error}`);
        }
        else{
            console.log(`transaction hash: ${transactionHash}`);
        }        
    });
});

La URL del HttpProvider corresponde a la dirección donde está a la escucha Ganache, la cual puedes copiar desde aquí:

Si recuerdas el artículo anterior, incluso desplegar un contrato es una transacción dentro de Ethereum, por lo que vamos a recuperar una de las cuentas que tenemos disponibles de prueba, para poder enviar dicha transacción a Ganache. Una vez que tenemos una cuenta, creamos una instancia del contrato, utilizando el ABI guardado anteriormente, y a través de la función deploy lo desplegamos en la red, mandando el bytecode, la cuenta que va a hacer la transacción y el gas que aportamos para ello.

Si vuelves a ejecutar el script completo, la compilación y el despliegue del contrato, tendrás como resultado el hash de la transacción generada para la creación del smart contract. Además, si echas un vistazo a la UI de Ganache verás que la primera cuenta ha perdido ethers al lanzar la creación del contrato.

Ganache – la primera cuenta pierde eth al desplegar el contrato

en el apartado Blocks verás que se ha creado un nuevo bloque:

y en el apartado de las transacciones tendrás una nueva, que se corresponde con la creación del contrato.

Probar el contrato desplegado en Ganache

Para comprobar que el contrato funciona correctamente, vamos a finalizar el artículo lanzando algunas llamadas. Normalmente esto no se hace así, ya que la creación de contratos es independiente del cliente, pero es para que tengas el flujo completo. En la llamada de callback, donde nos devuelve el hash de la transacción, vamos a recuperar la dirección del contrato en la red de Ethereum que nos está proporcionando Ganache. Con ella creamos una instancia del contrato donde le pasamos, además del ABI, la dirección de la instancia.

 async (error, transactionHash) => {

        if (error) {
            console.log(`error: ${error}`);
        }
        else {
            console.log(`transaction hash: ${transactionHash}`);

            let receipt = await web3.eth.getTransactionReceipt(transactionHash);

            console.log('Contract address: ' + receipt.contractAddress);

            let contractDeployed = new web3.eth.Contract(abi, receipt.contractAddress);

            contractDeployed.methods.Register.send({ from: creatorAccount }, (err, data) => {
                if (err) {
                    console.log(`error: ${err}`);
                    return;
                }
                contractDeployed.methods.Register.send({ from: creatorAccount }, (err, data) => {
                    if (err) {
                        console.log(`error: ${err}`);
                        return;
                    }

                    contractDeployed.methods.GetMyRegistries.call({ from: creatorAccount }, (err, data) => {
                        console.log(data);
                        console.log(data.toString());
                    });
                });

            });
        }

Para generar algo de datos, llamo dos veces a la función Register con contractDeployed.methods.Register.send donde le paso la cuenta que llevará a cabo la transacción, la cual registra el fichaje (en este ejemplo, por simplicidad he utilizado la misma que creó el contrato) y, después de llamarla dos veces, por último llamo al método GetMyRegistries a través de contractDeployed.methods.GetMyRegistries.call para que me devuelva los fichajes de la cuenta. El resultado será parecido al siguiente:

Probando el contrato desplegado en Ganache

Como ves, el resultado se devuelve en formato hexadecimal. Es por ello que en el ultimo console.log he utilizado toString para ver los timestamps que corresponden a los fichajes en hexadecimal, tal y como vimos en el IDE de Remix. Si nos fijamos ahora en la interfaz de Ganache veremos que tenemos transacciones nuevas, en este caso del tipo Contract Call al llamar dos veces a la función Register.

Transacciones del tipo Contract Call al llamar a Register

No tenemos rastro de la llamada a GetMyRegistries, ya que al ser solo de lectura, ¡y gratuita!, no genera transacciones.

¡Saludos!