Los bugs de un smart contract podrían arruinar tu apuesta del próximo Real Madrid-Barcelona

jueves, 20 de abril de 2017

Tan solo quedan 80 horas para uno de los partidos más importantes de la temporada futbolística: Real Madrid - FC Barcelona. Y como de costumbre, solemos hacer apuestas sobre estos partidos entre compañeros de trabajo.

De forma habitual, un compañero confiable debería hacerse responsable de la recaudación de los participantes que se encuentren en la oficina de forma presencial, además de tener que encontrarse disponible, al menos, hasta el día del partido pudiendo darse la opción de que surgieran agentes boicoteadores. Sin embargo, como no queremos dejar de lado a nuestros compañeros de las diferentes sedes de ElevenPaths y tampoco hemos identificado a ese compañero «confiable durante 80 horas», hemos encontrado la solución perfecta: crear una aplicación descentralizada o dapp sobre la red de Ethereum.

Pasos para programar un contrato en Ethereum

Esta plataforma, conocida por muchos como Bitcoin 2.0, ofrece la posibilidad de crear contratos de forma nativa en sus propios clientes utilizando un lenguaje específico llamado Solidity (muy parecido a Javascript). En este sentido, el primer paso será preparar nuestro contrato ya sea mediante el cliente de Ethereum o utilizando herramientas online como Browser Solidity que nos irán avisando de los posibles errores de compilación que podamos haber cometido en el proceso de programación del contrato. El código fuente del contrato que hemos preparado está disponible aquí.

El segundo paso tratará sobre la lógica del mismo. Los contratos comienzan siempre con la palabra reservada «contract». El nombre elegido del contrato será el mismo que definamos en el constructor que es la función que será llamada cuando el contrato sea desplegado por el organizador del evento. Este constructor solamente se invocará una vez durante su creación con lo que debe hacerse cargo de la inicialización de las variables que hayamos definido a lo largo del mismo.

En nuestro caso, el organizador podrá definir el contrato en base a los siguientes parámetros:

  • Por un lado, el precio de la apuesta en finneys (la unidad básica de Ethereum es el ether pero 1 ether equivale a 1000 finneys) teniendo en cuenta que el cambio actual ronda los 50 dólares por ether.
  • Por otro, Los detalles del evento así como unas pequeñas instrucciones sobre los tipos de apuestas considerados válidos. Aunque son totalmente prescindibles, es una forma sencilla de mostrar al usuario sobre qué evento está apostando.
  • Por último, la duración en horas del evento, período durante el cual permitiremos a los participantes enviar sus pronósticos. El objetivo perseguido es limitar la hora máxima para poder apostar con el fin de evitar que alguien cambie el resultado una vez empezado el partido (o incluso después de que haya terminado). Para realizar la conversión utilizamos dos palabras reservadas: «now», que representa el momento actual, y «* 1 hours», que es un pequeño truco para convertir un número a la forma de representar el momento actual que tiene Ethereum.

Figura 1. Opciones del contrato.

A partir de ahí, la lógica del contrato tiene dos partes principales:

  • hacerPronostico(). Esta función se encarga de gestionar las operaciones de inclusión de nuevos pronósticos por parte de los participantes. La primera comprobación que realizamos es si la hora en la que se invoca la petición es anterior o posterior a la hora límite ya definida en el constructor. En caso afirmativo, verificaremos si la cantidad enviada por el apostante («msg.value») es exactamente la fijada. En caso de que cualquiera de estas dos condiciones no se satisfaga, se registrará en un log que se podrá consultar desde otras aplicaciones. En caso afirmativo, se registrará el pronóstico en un diccionario («mapping») en el que la clave será el resultado y el valor una lista de direcciones que han apostado a ese resultado. Como Ethereum es un proyecto en desarrollo, ha habido que hacer un pequeño workaround porque no está implementado aún el uso de «strings» como claves de diccionario. Para ello usamos la función «_stringAsKey» que convierte una cadena de texto en formato «bytes32». Menos usable, pero funcional.

    Aunque es prescindible, para que el usuario pueda ver qué apuestas ha realizado una dirección concreta rápidamente, también hemos incluido un nuevo diccionario en el que las claves son de tipo «address» y el valor es una lista de «strings» que en este caso almacenará los resultados. El valor de la dirección que ha emitido el pago es «msg.sender» y es el que registraremos en ambos diccionarios así como en los diferentes eventos que iremos logueando.

    Figura 2. Definición de la primera parte del contrato.

  • pagarGanadores(). Esta función es la de gestión del pago. En nuestro caso, queremos que sea la dirección del organizador la única que pueda indicar al contrato cuál es el resultado final del partido para lo que haremos la comprobación de si el «msg.sender» que invoca la función es la dirección definida como «organizador» en el constructor. Si por error u omisión no lo hiciéramos, cualquier persona podría indicar el resultado del partido y llevarse los ethers del resto de apostantes con lo que un error en la lógica implementada por el programador podría derivar en la pérdida del dinero almacenado en el contrato.

    Afortunadamente, nuestro proceso de resolución es sencillo. Se recoge el resultado facilitado por el organizador y se recupera la lista de ganadores del diccionario pronósticos (nuevamente, hacemos la conversión a «bytes32» que hemos hecho antes). Una vez que sabemos cuántos apostantes han acertado, calculamos el saldo a pagar a cada uno multiplicando el precio por el total de pronósticos y dividiéndolo entre los ganadores para luego recorrer la lista de ganadores y enviar el pago con la función «send(pago)» sobre cada dirección.

    Es cierto que en Ethereum pueden quedar restos pero el orden de magnitud es muy pequeño ya que la unidad mínima, el wei, es 10-18 ethers. Aunque sean ridículos, una forma de terminar el contrato puede ser enviar al organizador los restos que queden con el comando «suicide(organizador)» en ese momento.

    Figura 3. Definición de la segunda parte del contrato.

La fase del despliegue del contrato

En este punto, partimos de la base de que los apostantes tendremos la herramienta Mist instalada para gestionar nuestros ethers, que habremos comprado con anterioridad en algún mercado, así como para lanzar nuestro contrato. Aunque nosotros sí que hemos utilizado ether reales, los que queráis curiosear podéis utilizar también la Testnet en lugar de la Main Network y minar algunos ether de prueba. Estos ether no tienen validez en la red principal, pero sí que se pueden utilizar para al menos familiarizarse con la tecnología. Una vez que tengamos esos requisitos resueltos, tendremos que ir hasta «Contracts» y pulsar en «Deploy new contract».

Figura 4. Despliegue del contrato.

Tendremos que señalar la dirección que será la que asociemos al contrato y que será la que hará frente al gasto del despliegue del contrato. Si el contrato no da errores de compilación una vez copiado el código fuente podremos invocar al constructor desde la interfaz y nos saldrán las opciones definidas anteriormente ahí mismo.

Figura 5. Invocamos al constructor desde la interfaz.

Una vez preparado todo, nos requerirá la contraseña que habremos utilizado para proteger nuestra clave privada asociada a la dirección que desplegará el contrato. Esa contraseña debería ser robusta para evitar que un tercero con acceso al equipo la pudiera adivinar fácilmente y llevarse nuestro saldo.

Figura 6. Contraseña con la que se protege la clave privada.

Interactuando con el contrato

Una vez el contrato ha sido añadido a la cadena de bloques, tendremos que facilitar al resto de participantes cierta información para que sepan cómo interactuar con él y puedan realizar sus pronósticos. La información que tendríamos que compartir sería la dirección del contrato, en nuestro caso sería 0x23222e07c972fB5A0b5A007749A4a0F6735C3350, así como la interfaz JSON de este que es la encargada de decir a la aplicación qué formas de interactuar con él están disponibles. Esta información habría que añadirla desde el menú de «Watch contract».

Figura 7. Interfaz del contrato en formato JSON.

Una vez registrado el contrato, ya lo podremos seleccionar para hacer nuestra apuesta, eligiendo en la parte de la derecha la opción de «Hacer Pronóstico» e introduciendo el resultado (en nuestro caso, los goles separados por un guion y sin espacios) y la cantidad de finneys a abonar.

Figura 8. Ejemplo de interactuación con el contrato para colocar un pronóstico.

Conclusiones

De esta manera y de forma más o menos sencilla y sin apenas recursos, hemos preparado una prueba de concepto de lo que podría ser un sistema de apuestas descentralizadas difícil de controlar. Aunque esto es solo una prueba entre compañeros de trabajo, el negocio sobre Ethereum está evolucionando a pasos agigantados y no se le está dando la importancia necesaria a la seguridad. No podemos perder de vista que el código lanzado no deja de ser código ejecutado en un entorno virtualizado en el que, por error u omisión, al programador se le podría olvidar realizar ciertas comprobaciones como, por ejemplo, la de reembolso o la de terminar el contrato antes de tiempo. En algunos casos, podemos tener la suerte de dar con una empresa a la que poder reclamar nuestro dinero, pero, en otros es probable que el usuario tenga que resignarse a perder su inversión porque no exista ninguna entidad física o jurídica fácil de señalar como responsable de los errores derivados de estas operaciones. Mientras tanto, ¿te fías de nuestro código?


Yaiza Rubio
Intelligence Analyst at ElevenPaths
@yrubiosec

Félix Brezo
Intelligence Analyst at ElevenPaths
@febrezo

1 comentario:

  1. Hola Señor / Señora, déjeme decirle
    contacto en este Día. Me gustaría informarle de que soy un particular que ofrece préstamos financieros para ayudar a la gente en necesidad. No somos sin ignorado la crisis que atraviesa nuestro continente se ha debilitado nuestros bancos e instituciones financieras. Me gustaría compartir mi oferta de crédito o de préstamo de dinero entre particular, este préstamo es accesible a todos, tanto a los desempleados, prohibido o archivo del banco. Yo también a personas que están en necesidad y la búsqueda de ayuda financiera para proyectos o para pagar sus deudas. Te ofrezco un préstamo de 500 € a 50 000 € a todas las personas capaces de devolver con intereses a la tasa del 2% para cualquier persona interesada escribirme,
    contatto:credito.locali.veloce@gmail.com

    ResponderEliminar