Ethereum es una plataforma descentralizada que ejecuta contratos inteligentes: aplicaciones que se ejecutan exactamente como se programaron sin posibilidad de tiempo de inactividad, censura, fraude o interferencia de terceros.

Los contratos inteligentes se programan habitualmente con Solidity que es una mezcla de varios lenguajes: C++, Javascript y Python, y es a mi modo de ver todavía un poco limitado.

Gracias a solidity y a varias tecnologías ya conocidas como html, css y javascript, se pueden crear Dapps (aplicaciones distribuidas). Cabe pensar que estas aplicaciones tienen parte de su backend en una blockchain (ya sea privada o pública). Es un cambio de paradigma muy interesante y es tendencia actualmente.

Como sugerencia, te recomiendo algunos libros de html, libros de css, libros de javascript y libros de Ethereum que sirven para entrar en detalle en estas materias.

Aunque este curso de Ethereum con solidity se ha creado a fecha de Febrero de 2018, actualmente Ethereum y solidity evolucionan rápidamente, y algunas sentencias y APIs van quedando obsoletas, por eso es probable que encuentres muchos ejemplos en Internet que ya no funcionen.

El curso de solidity está compuesto de una parte práctica interactiva y otra parte teórica (en la zona inferior).

Para realizar pruebas de compilación de contratos inteligentes con solidity se puede usar el editor siguiente. Se pueden desplegar los contratos inteligentes en la red de test - Ropsten.

Instrucciones para usar la parte práctica
  • Para compilar el contrato inteligente apretar el botón "Compilar".
  • Para conocer el saldo de una cuenta (address) informar la clave pública y apretar el botón "Saldo".
  • Para desplegar el contrato inteligente se puede usar Metamask o informar la address (que tenga al menos 0.1 Ether) y la clave privada y apretar el botón "Desplegar contrato usando clave...".
  • Para ver el detalle de la transacción informar el hash tx y apretar el botón "Ver transacción".
  • Para ver el recibo de la transacción (address del contrato) informar el hash tx y apretar el botón "Ver recibo".
  • Para ver el detalle del bloque informar el bloque y apretar el botón "Ver bloque".

Editor de solidity


Detalles del contrato

     

    Red Ethereum de test - Ropsten

    Wallet

    ... Ether

      Buscador de bloques y transacciones

       

      Detalle del bloque

      Detalle de la transacción

      Detalle del recibo

      Si este curso de solidity te parece útil, puedes realizar una colaboración en Ether a la address: 0x49Afd3923c1040a1f0309d3C5F62c26CD65127F0

      También nos gustaría recibir tu opinión, dudas y comentarios en el formulario de contacto.

      El lenguaje Solidity en detalle

      Un contrato inteligente se ubica en una dirección concreta (address) dentro de la blockchain de Ethereum.

      Los contratos se ejecutan dentro de una máquina virtual de Etherium (EVM). El código que ejecuta no tiene acceso a la red, al sistema de ficheros,o a otros procesos. Sólo tienen acceso limitado a otros contratos inteligentes que se ubican en la blockchain.

      Existen dos tipos de cuentas: las cuentas externas que se controlan por pares de claves (pública/privada) y las cuentas de contratos que se controlan por el código fuente del contrato. La cuenta de un contrato se crea en el momento que se desplega el contrato en la blockchain.
      Cada cuenta tiene un saldo en unidades de wei (ether) que se puede modificar enviando transacciones que incluyan un valor.

      Una transacción es un mensaje que se envía de una cuenta a otra. Puede incluir datos binarios (payload) y ether.

      Una llamada es un mensaje que no involucra ether. Por ejemplo una llamada a un método de un contrato.

      Cada transacción se carga con una cantidad de gas, que se consume durante su ejecución. El creador de la transacción fija el precio del gas, y debe pagar un total = precio del gas * gas utilizado de su cuenta de ether. Si sobra gas al realizar la transacción, después de la ejecución se le retorna.

      Cada cuenta de contrato dispone de una región de memoria persistente que se llama "storage". También cuenta con otra región de memoria que se llama "memory" que se crea en cada llamada de mensaje.

      Las llamadas de mensajes se usan para invocar contratos desde otros contratos o para enviar ether a cuentas que no son de contratos. Son similares a las transacciones.

      Los contratos pueden cargar código dinámico de otra dirección. El "storage", address y el saldo hacen referencia al contrato que hace la llamada. Esto posibilita implementar librerías en solidity.

      Los contratos pueden guardar logs en una región especial de la blockchain, y se usa en el manejo de eventos. Los contratos no pueden acceder a los logs después de crearlos, pero éstos se pueden ver desde fuera de la blockchain, por ejemplo desde un "cliente ligero".

      Solidity, el lenguaje para programar contratos inteligentes

      Podemos pensar que un contrato es como en una clase en programación orientada a objetos.

      Los atributos serían las variables de estado y las propiedades serían las funciones que pueden modificar estas variables de estado.

      Tendría también su constructor (sólo se permite uno) aunque es opcional.

      Para definir un contrato se usa la sentencia pragma con la versión de solidity y luego contract:

      pragma solidity ^0.4.18;
      
      contract GuardarDato {
        uint dato;
      
        function set(uint x) public {
          dato = x;
        }
      
        function get() public constant returns (uint) {
          return dato;
        }
      }

      Este contrato de ejemplo no hace nada especial, sólo guarda un dato que es un número entero. Y cualquiera podría llamar al método set para cambiarlo.

      Dentro de un contrato definimos habitualmente eventos, estructuras de datos y enums.

      Las funciones pueden tener modificadores que cambian el comportamiento. Por ejemplo la visibilidad: como privado o público.

      Las variables también pueden tener visibilidad, así una variable privada es visible por el resto de actores del blockchain, pero no es accesible desde el resto de contratos.

      Una variable pública se puede leer por el resto de contratos y actores.

      Las variables de estado se almacenan permanentemente en el contrato.

      Los comentarios se escriben con // para una sola línea o con /* .... */ para varias líneas.

      Los ficheros de código fuente de solidity suelen tener la extensión .sol y usan la guía de estilos pep8 heredada de Python.

      Herencia

      Los contratos pueden heredar unos de otros con la sentencia is.

      La herencia múltiple está permitida.

      contract micontrato is contratopadre { 
        variables
        funciones 
      }

      Operador new

      Un contrato puede crear otro contrato con la sentencia new. Se puede enviar ether al crear el contrato invocando al método value() después del constructor.

      Destrucción

      Un contrato se puede destruir con la llamada selfdestruct(address), enviando el ether acumulado a esa address. Es la única manera de eliminar un contrato de la blockchain. Aunque siempre es recomendable tener una variable de estado para desactivarlo que haga que se lancen throws al invocar sus funciones, así el ether será retornado.
      Es recomendable guardar el msg.sender en el constructor y luego pasarlo como argumento en selfdestruct.

      Contratos abstractos

      Son contratos que tienen algunas funciones sin implementación. Es decir Se define el nombre de la función, los parámetros de entrada y de salida, y se finaliza con un punto y coma. Estos contratos no se compilan, se usan como contratos base para otros contrato que los heredan.

      Interfaces

      Los interfaces son similares a los contratos abstractos, pero no pueden tener ninguna función implementada. Tampoco pueden heredar de otros contratos o interfaces, no pueden definir un constructor, ni variables, ni structs, ni enums. Se usan en los contratos que los heredean.

      Existe un compilador muy completo online: Remix, un compilador de solidity en la nube o EthFiddle que es otro compilador en línea más limitado para Solidity.

      Solidity es un lenguaje tipado estáticamente, lo que significa que el tipo de cada variable(de estado y local) se tiene que especificar en tiempo de compilación. El lenguaje proporciona ciertos tipos de variables básicos, que se pueden combinar para formar tipos complejos.

      Tipos de variables

      bool: describe un valor que puede ser verdadero o falso. (true/false)

      uint: describen números enteros, con signo o sin signo de varios tamaños. El más pequeño es de 8 bits (uint8) y el más grande de 256 bits (uint256). Existen definiciones para todos los tamaños intermedios saltando de 8 en 8. Este tipo se usa también para cantidades y fechas (formato unix).

      Unidades de Ether: una cantidad puede tener un sufijo para determinar su medida. wei, finney, szabo, ether. Por defecto es wei.

      Unidades de tiempo: una cantidad puede tener un sufijo para determinar el tiempo. seconds, minutes, hours, days, weeks, years. Por defecto es seconds.

      address: es un tipo especial de 20 bytes que almacena una dirección de wallet de ethereum. Este tipo tiene 2 propiedades: balance (para conocer el saldo) y transfer (para enviar ether en unidades de Wei a un dirección.

      Nota: El Wei es la unidad más pequeña de Ether. 1 Ether equivale a 1018 Wei.

      msg: hace referencia al mensaje recibido por el contrato. Sus propiedades son sender (la address del que envía), value (cantidad de ether en wei), data (los datos de la llamada), gas (el gas residual).

      Nota: Si el método es el constructor entonces msg.sender será el creador del contrato.

      now: la fecha actual en formato unix (uint), es equivalente a block.timestamp.

      block: datos del bloque. Tiene las propiedades number (número de bloque actual), difficulty (dificultad actual del bloque), gasLimit (limite de gas del bloque).

      storage: almacenamiento persistente de hash.

      arrays fijos: son arrays con longitud fija como byte1, byte2, ...byte32 y tienen la propiedad length que muestra la longitud del array.

      arrays dinámicos: son arrays de longitud variable como bytes, string (codificado en UTF-8). Las cadenas de literales se encierran entre comillas dobles o simples. Soportan carácteres de escape especiales como \n y \uNNNN. También hay literales hexadecimales definidos con hex. Los arrays dinámicos se puede redimensionar si se encuentran en el storage (no en memoria) cambiando el valor de su propiedad length. Estos arays también tienen la función push que sirve para añadir un elemento al final del array. Esta función retorna la nueva longitud del array.

      enums: sirven para crear tipos específicos definidos por el usuario.

      struct: son tipos definidos por el desarrollador que agrupan varias variables.

      diccionarios (clave/valor): se crean con mapping(tipo de la clave => tipo del valor).

      this: hace referencia a la address del contrato, así pues this.balance es el saldo acumulado en el contrato.

      Las variables se pueden inicializar o limpiar con la sentencia delete. Pone a 0 los valores.

      Como el contenido de las variables se puede ver en la blockchain, desde fuera del contrato, se puede ocultar el valor mediante hashing.

      Constantes

      Las variables de estado se pueden declarar como constantes. Por ello se tienen que asignar a un valor en tiempo de compilación. Sólo se permite valores numéricos o strings. Y también se permite asignarlas a funciones de hash (keccask256, sha256, etc).

      Persistencia

      Las variables complejas, como arrays y structs, tienen un modificador extra para indicar si queremos guardarla en memoria (memory) o en almacenamiento (storage). Por defecto es en memoria para los argumentos de las funciones y su retorno, en cambio para las variables de estado el valor por defecto es storage.

      Las funciones en solidity se especifican del siguiente modo:

      function () {internal|external} [pure|constant|view|payable] [returns ()]

      Pueden ser internas o externas. Los tipos de retorno no pueden estar vacíos. Sino retorna nada, no especificar la sentencia returns.

      Las funciones pueden retornar más de un parámetro, especificado cada uno dentro de la sentencia "returns".

      Por defecto las funciones son internas, con lo que habitualmente se omite este modificador. Las internas sólo son visibles en el contrato actual y los que heredan de él.

      Visibilidad

      Las funciones pueden ser externas, internas, privadas o públicas. Por defecto son públicas.

      external:

      Las funciones externas forman parte del interfaz del contrato y se pueden llamar desde otros contratos y vía transacciones. No se pueden llamar internamente.

      public:

      Las funciones públicas forman parte del interfaz del contrato y se pueden llamar internamente o vía mensajes. Para las variables de estado públicas se genera automáticamente una función getter.

      internal:

      Las funciones y variables de estado internas sólo se pueden acceder internamente (desde el contrato actual o contratos que derivan de este) sin usa this.

      private:

      Las funciones y variables de estado privadas son sólo visibles en el contrato en el que están definidas, y no en contratos heredados.

      Sobrecarga

      Un contrato puede tener varias funciones con el mismo nombre pero con argumentos diferentes. Esto también aplica a funciones heredadas de otro contrato.

      Modificadores

      El modificador constant indica que la función no puede cambiar la variable. Se ejecuta localmente, no en el blockchain. Su uso está deprecado en favor de pure o view.

      El modificador view indica que la función no modifica el estado.

      Nota: se considera modificar el estado: escribir el valor de la variable, disparar un evento, crear otro contrato, usar selfdestruct, enviar ether via llamadas, invocar a otra función que no sea pure o view, usar llamadas a bajo nivel, usar código ensamblador que contenga ciertos opcodes.

      El modificador pure indica que la función no cambia una variable ni la lee.

      Nota: se considera leer: leer el valor de una variable de estado, acceder a this.balance, o address.balance, acceder a métodos de block, tx, o msg, excepto msg.sig y msg.data, invocar a otra función que no sea pure, usar código ensamblador que contenga ciertos opcodes.

      El modificador payable indica que la función puede recibir ether.

      Se pueden crear modificadores personalizados para las funciones con la sentencia modifier. Por ejemplo esta es una que valida que sólo lo invoque el creador del contrato:

      modifier soloCreador {
        if (msg.sender == owner)
        _
      } 

      Se puede llamar a funciones de otros contratos con llamadas externas, en ese caso se puede indicar el valor de wei y gas enviado con los métodos .value(v).gas(g)

      Estas llamadas pueden lanzar excepciones si no existe el contrato, o el método externo lanza una excepción.

      Se suele definir una función de fallo, en caso de que se envien datos incorrectos, o se envía ether sin datos:

      function () {
        throw;
      }

      Para recibir ether, debe marcarse como payable, sino existe no se puede enviar ether al contrato mediante transacciones. Excepto en el caso de ser el destino de una transacción coinbase (recompensa de un minero) o de un destino de selfdestruct.

      Estructuras de control de flujo

      Se permite la mayoría de sentencias de control que se usan en Javascript excepto switch y goto. Así pues podemos usar: if, else, while, do, for, break, continue, return, ?:

      Los paréntesis no se pueden omitir, y las llaves tampoco, a menos que sea una única asignación simple.

      Throw

      Se pueden lanzar excepciones, pero no se pueden capturar. La sentencia throw restablece el ether al sender y el estado.

      Tratamiento de errores

      El tratamiento de errores es muy básico en solidity. Existen varias sentencias para gestionar errores:
      assert (condicion) (hace un throws si la condición no se cumple, por ejemplo errores internos),
      require (condicion) (hace un throws si la condición no se cumple, por ejemplo para validar entradas),
      revert (aborta la ejecución y restablece los cambios de estado).

      Iteradores

      Las iteraciones estan limitadas en solidity, se puede recorrer un array, pero los mapas no se pueden recorrer sin conocer las "claves".

      Funciones matemáticas y criptográficas

      Tenemos disponibles varias funciones de hashing y de suma y multiplicación módulo.

      addmod(uint x, uint y, uint k) returns (uint):

      calcula (x + y) % k donde la suma se realiza con precisión arbitraria y no ajusta a 2**256. Assert que k != 0 desde la versión 0.5.0.

      mulmod(uint x, uint y, uint k) returns (uint):

      calcula (x * y) % k donde la suma se realiza con precisión arbitraria y no ajusta a 2**256. Assert que k != 0 desde la versión 0.5.0.

      keccak256(...) returns (bytes32):

      calcula el hash Ethereum-SHA-3 (Keccak-256) de los argumentos empaquetados

      sha256(...) returns (bytes32):

      calcula el hash SHA-256 de los argumentos empaquetados

      sha3(...) returns (bytes32):

      es un alias de keccak256

      ripemd160(...) returns (bytes20):

      calcula el hash RIPEMD-160 de los argumentos empaquetados

      ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):

      recupera la address asociada a la clave pública de la firma de la curva elíptica o retorna cero si hay error

      Nota: argumentos empaquetados significa que los argumentos se concatenan sin padding (relleno).

      En solidity se pueden definir eventos con la sentencia event MiEvento(a) y posteriormente se puede disparar ese evento llamando a MiEvento(a).

      Esto es útil para llamar a funciones Javascript de callback, en la parte de la vista de una Dapp (aplicación distribuida).

      Los argumentos se almacenan en el log de la transacción, una estructura de datos especial que hay en la blockchain.

      Los datos de los eventos no se pueden consultar desde dentro de un contrato.

      Y en Javascript:

                var abi = /* abi generado por el compilador */;
                var ClientReceipt = web3.eth.contract(abi);
                var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
      
                var event = clientReceipt.Deposit();
      
                // esperar a que hayan cambios
                event.watch(function(error, result){
                    if (!error)
                    console.log(result);
                    });
      
                // O pasar una función de callback para ver cambios inmediatamente
                var event = clientReceipt.Deposit(function(error, result) {
                    if (!error)
                    console.log(result);
                    });