Tutorial de solidez

Solidity es un lenguaje de alto nivel orientado a objetos para desarrollar dApps (aplicaciones descentralizadas), en la cadena de bloques Ethereum.

Una cadena de bloques es una red de computadoras peer-to-peer, llamadas nodos, que comparten todos los datos y el código en la red..

Entonces, si eres un dispositivo conectado a la cadena de bloques, eres un nodo en la red y hablas con todos los demás nodos de la computadora en la red (hablaremos sobre cómo configurar el nodo Ethereum en tu máquina local en tutoriales posteriores).

Ahora tiene una copia de todos los datos y el código en la cadena de bloques. Ya no hay necesidad de servidores centrales.

Que es Ethereum?

En su forma más simple, Ethereum es una plataforma de software abierta basada en la tecnología blockchain que permite a los desarrolladores crear e implementar aplicaciones descentralizadas..

Mientras que la cadena de bloques de Bitcoin se utiliza para rastrear la propiedad de la moneda digital (bitcoins), la cadena de bloques de Ethereum se centra en ejecutar el código de aplicaciones descentralizadas..

En la cadena de bloques Ethereum, en lugar de extraer bitcoins, los mineros trabajan para ganar Ether, un tipo de token criptográfico que alimenta la red. Más allá de una criptomoneda negociable, los desarrolladores de aplicaciones también utilizan Ether para pagar tarifas de transacción y servicios en la red Ethereum..

Hay un segundo tipo de token que se utiliza para pagar las tarifas de los mineros por incluir transacciones en su bloque, se llama gas, y cada ejecución de contrato inteligente requiere que se envíe una cierta cantidad de gas junto con él para atraer a los mineros a ponerlo en la cadena de bloques.

Empezando por lo básico

El código de Solidity está encapsulado en contratos.

Ethereum blockchain nos permite ejecutar código con la máquina virtual Ethereum (EVM) en blockchain con algo llamado contrato inteligente..

Los contratos inteligentes son el lugar donde vive toda la lógica empresarial de nuestra aplicación: todas las variables y funciones pertenecen a un contrato, y este será el punto de partida de todos sus proyectos..

Los contactos inteligentes están escritos en un lenguaje de programación llamado Solidity, que parece una mezcla de Javascript y C.

Remix IDE

Remix es una herramienta en línea que le permite escribir contratos inteligentes de Solidity, luego implementarlos y ejecutarlos.

Solo ve a https://remix.ethereum.org desde su navegador y podemos empezar a codificar.

Como puede ver, puede elegir entre Solidity y Vyper. Ambos son lenguajes para escribir contratos inteligentes, Vyper es similar a python y Solidity es similar a javascript.

Ambos pueden compilar en el código de bytes EVM, algo así como Javascript y Typecript. Elegimos Solidez.

En el lado izquierdo está el explorador de archivos. De forma predeterminada, hay dos archivos .sol, solo para demostrar la sintaxis básica (ballot.sol es contrato inteligente, ballot_test.sol es un script para probar ese contrato inteligente).

Solo necesita hacer clic en ese botón más y podemos comenzar a codificar nuestro primer contrato inteligente.

Todo el código fuente de solidez debe comenzar con una “versión pragma”, una declaración de la versión del compilador de Solidity que debe usar este código. Esto es para evitar problemas con futuras versiones del compilador que podrían introducir cambios que podrían romper su código..

Se parece a esto:

solidez del pragma ^ 0.4.25;

(para la versión Solidity por encima de 0.4.25)

o

solidez del pragma >= 0.5.0 < 0,6,0;

(para la versión Solidity entre 0.5.0 y 0.6.0)

Luego, crea su contrato escribiendo la palabra reservada contrato y el nombre de su archivo .sol (es importante que el nombre del contrato coincida con el nombre del archivo, discutiremos por qué más adelante). En nuestro caso,

contract MyFirstContract {

}

Compilemoslo. Solo necesita navegar a esa pestaña de compilación a la izquierda y hacer clic en el botón de compilación grande. Si algo está mal con el código, verá errores y advertencias aquí (sea compasivo con Solidity, todavía es un “lenguaje joven”).

Con nuestro contrato actual todo está bien porque realmente no hemos hecho nada.

Ahora generaré un error a propósito solo para mostrarte algo. Puede seleccionar manualmente el compilador de ese menú desplegable.

Elijamos, por ejemplo, la versión 0.4.26. Ahora compílelo de nuevo. Ahora verá un error “El compilador aún no se ha cargado”..

Esto se debe a que especificamos con pragma para trabajar con versiones del compilador superiores a 0.5.0. Simplemente cambie la versión del compilador nuevamente, y el error desaparecerá.

Bien, codifiquemos ahora!

Comenzaremos con un código simple “Hola mundo” y obtendremos y configuraremos funciones, solo para familiarizarnos con la sintaxis..

Un contrato en el sentido de Solidez es una colección de código (sus funciones) y datos (su estado) que residen en una dirección específica en la cadena de bloques Ethereum..

Primero, definamos la variable de estado llamada mensaje, por ejemplo, y su tipo será cadena.

Nuestra función get devolverá el valor de nuestro mensaje variable, y la función set asignará un nuevo valor a nuestro mensaje variable.

Cómo escribir funciones?

Primero, palabra reservada función luego el nombre de la función y los parámetros particulares y después de eso .

function myFunction () devuelve (bool) {

devuelve verdadero;

}

Las funciones pueden ser público o privado. Si una función es pública, se puede llamar fuera de contrato. Si una función es privada, tiene un alcance limitado y solo se puede llamar desde su contrato actual (desde alguna otra función, por ejemplo).

Aquí está la lista de todos los especificadores de visibilidad de funciones:

  • público: visible externa e internamente (crea una función getter para las variables de almacenamiento / estado)
  • privado: solo visible en el contrato actual
  • externo: solo visible externamente (solo para funciones), es decir, solo se puede llamar por mensaje (a través de this.func)
  • interno: solo visible internamente

Las funciones pueden ser puro, ver, o pagadero. Si una función no escribe ningún dato en blockchain, es muy recomendable ser vista, porque las funciones de vista no cuestan nada..

Aquí está la lista de todos los modificadores de funciones (también hay modificadores para variables de estado, eventos y argumentos de eventos, pero hablaremos de ellos más adelante):

  • puro: No permite la modificación o el acceso del estado.
  • ver: No permite la modificación de estado.
  • pagadero: Les permite recibir Ether junto con una llamada.

Si la función devuelve algún valor, debe especificarlo con palabra reservada devoluciones y luego entre paréntesis regulares para especificar qué tipo devuelve la función. En nuestro caso, será una cadena (porque devolvemos nuestro mensaje variable que es una cadena)

Si la función no devuelve ningún valor, no es necesario devoluciones declaración.

Para acceder a una variable de estado, no necesita el prefijo esta. como es común en otros idiomas.

Por eso, una práctica común es escribir argumentos de función con sintaxis de subrayado (_mensaje). Esta convención proviene de Javascript, donde los métodos privados y las variables comienzan con _.

Para ser claros, su código funcionará bien y sin guiones bajos, pero es más limpio con ellos.

Notarás palabra reservada memoria en nuestro código. Si escribe nuestro código sin memoria y establece pragma en alguna versión por debajo de 0.5. * Funcionará bien, pero cuando cambie su compilador por encima de 0.5. * EVM genera un error de compilación.

Por qué pasó esto?

Bueno, la máquina virtual Ethereum tiene tres áreas donde puede almacenar elementos.

  • El primero es almacenamiento, donde residen todas las variables de estado del contrato. Cada contrato tiene su propio almacenamiento y es persistente entre llamadas a funciones y bastante caro de usar..
  • El segundo es memoria, esto se usa para mantener valores temporales. Se borra entre llamadas a funciones (externas) y es más económico de usar.
  • El tercero es el apilar, que se utiliza para contener pequeñas variables locales. Es de uso casi gratuito, pero solo puede contener una cantidad limitada de valores.

Para casi todos los tipos, no puede especificar dónde deben almacenarse, porque se copian cada vez que se utilizan.

Pero cuando trabaja con matrices o estructuras, y desde las últimas versiones con cadenas también, el compilador lo obligará a especificar el área de almacenamiento.

Entonces, nuestro código ahora se ve así:

solidez del pragma ^ 0.5.0;

contract MyFirstContract {

mensaje de cadena;

función get () devuelve la vista pública (memoria de cadena) {

mensaje de retorno;

}

conjunto de funciones (cadena de memoria _mensaje) público {

mensaje = _mensaje;

}

}

Tenga en cuenta que algunos desarrolladores de Solidity dividen esos especificadores de visibilidad en líneas separadas para hacer el código más limpio. Entonces nuestra función get se puede escribir así:

función get ()

público

ver

devuelve (cadena)

{

mensaje de retorno;

}

Depende realmente de usted cómo elige escribir sus funciones.

Compilemos nuestro contrato ahora y probémoslo.

Para compilarlo, simplemente repita los pasos a continuación (Compilar .sol botón o cmd / ctrl + S desde el teclado y lo recompilará automáticamente)

Para ver realmente cómo funciona (si la compilación no genera errores), debe implementar su contrato.

Para hacer eso, navegue a la pestaña Implementación de la izquierda, para el entorno seleccione JavaScriptVM y presione el botón Implementar.

Después de la implementación, ahora podemos ver los métodos de nuestro contrato. Centrémonos ahora solo en esa parte de la pantalla.

Puede ver que hay dos botones (obtener & set) para nuestras dos funciones públicas. Si alguno de esos fuera privado, no lo veríamos aquí..

Si hacemos clic en el botón obtener, EVM ejecutará nuestra función de obtención.

Veamos cómo funcionó.

Tenemos una cadena vacía. No es genial, no es terrible. ¿Pero por qué? Bueno, porque no inicializamos nuestra variable de mensaje en primer lugar.

Solo una pausa rápida. Quiero que me presente a Remix Terminal. Está en el editor de código y aquí puede rastrear todas sus transacciones, para ver si se ejecutaron con éxito o no, para depurarlas, ver detalles (hash de transacción, etc.) y más.

Por ahora, tenemos dos transacciones exitosas. Una es la implementación del contrato y nos cuesta éter (pero no te preocupes, estamos en el editor ahora todo es virtual) y la segunda es la llamada de nuestro ver función.

Ok, volvamos ahora. ¿Qué pasará si llamamos a la función set ahora??

Necesitamos pasar un _mensaje de argumento (“Hola mundo” por ejemplo) y presionar el botón de transacción para ejecutar la función. Puede realizar un seguimiento del éxito de la transacción en la Terminal.

Ahora llamemos a get function de nuevo. Ahora devuelve nuestro mensaje.

Realicemos algunas mejoras en nuestro código. No inicializamos nuestro mensaje variable. Hagámoslo.

contract MyFirstContract {

mensaje de cadena = "Hola Mundo!";

función get () devuelve la vista pública (memoria de cadena) {

mensaje de retorno;

}

conjunto de funciones (cadena de memoria _mensaje) público {

mensaje = _mensaje;

}

}

Observe que el mensaje ahora es “¡Hola mundo!”, Y cuando llamamos a la función get por primera vez, no devolverá una cadena vacía..

Para probar esto, necesitamos compilar nuestro contrato (cmd / ctrl + S).

Luego para desplegarlo de nuevo. Necesitamos crear una nueva instancia de contrato (debido a los cambios que hicimos) y publicarla en blockchain..

Simplemente elimine la versión anterior del editor (no de nuestra cadena de bloques virtual, por supuesto) y presione el botón Implementar nuevamente. Llamemos a nuestra función get ahora.

¡Lindo! Llamemos a la función set ahora.

Y conseguir de nuevo.

Frio.

Hagamos ahora de nuestro mensaje un constante.

Nuestro código ahora:

solidez del pragma ^ 0.5.0;

contract MyFirstContract {

mensaje constante de cadena = "Hola Mundo!";

función get () devuelve la vista pública (memoria de cadena) {

mensaje de retorno;

}

conjunto de funciones (cadena de memoria _mensaje) público {

mensaje = _mensaje;

}

}

Cuando intentamos compilarlo, obtenemos un error en nuestra función set. Eso es porque no se puede cambiar el valor de una constante.

Simplemente nos desharemos de esa constante ahora.

Inicializar variables como esta no es un error, pero es mucho mejor si lo hacemos en el constructor. Puede escribir constructor en Solidity con:

constructor () public {

// hacer algo…

}

Constructor es solo otra función que se llama durante la implementación de un contrato inteligente. Nuestro código se ve un poco diferente, pero funciona igual.

solidez del pragma ^ 0.5.0;

contract MyFirstContract {

mensaje de cadena;

constructor () public {

mensaje = "Hola Mundo!";

}

función get () devuelve la vista pública (memoria de cadena) {

mensaje de retorno;

}

conjunto de funciones (cadena de memoria _mensaje) público {

mensaje = _mensaje;

}

}

Puedes compilarlo de nuevo y probarlo si quieres.

Finalmente, se puede cambiar la visibilidad de las variables de estado. Si haces tus variables de estado público eso significa que uno puede reclamar sus valores desde fuera del contrato.

Solidity hará para cada variable de estado público un método con el mismo nombre que se puede llamar como una función regular (algo así como la función getter).

Esto significa que podemos deshacernos de nuestra función get, simplemente declare el mensaje variable como público, y nuestro código funcionará igual, será mucho más limpio y nos costará menos implementarlo algún día en la Red Principal.

Cuanto más grande es el código, más gas se necesita para ejecutarlo y aumenta el costo de ejecutar nuestra dApp.

Cuando desarrollamos contratos inteligentes, debemos ser:

  • eficiente – la tasa de gas consumida debe ser baja
  • preciso – una vez que implementa el contrato inteligente, no se puede cambiar y es público las 24 horas del día, los 7 días de la semana, cada línea de código (imagine un pirata informático que encuentra un error y puede explotar su dApp)

Nuestro código final para hoy se ve así:

solidez del pragma ^ 0.5.0;

contract MyFirstContract {

cadena de mensaje público;

constructor () public {

mensaje = "Hola Mundo!";

}

conjunto de funciones (cadena de memoria _mensaje) público {

mensaje = _mensaje;

}

}

Implementémoslo y probémoslo.

Puede ver ese botón de mensaje. Se está creando porque nuestro mensaje de variable de estado es público.

Si lo llamamos, debería devolvernos un valor que se inicializa a través del constructor (que es “¡Hola mundo!”).

Agradable. Probemos el funcionamiento del conjunto ahora.

Cómo aprender Solidez?

Solidity en sí es un lenguaje bastante simple, pero para ser un buen desarrollador de Solidity, es necesario comprender cómo funciona todo en Ethereum..

  • Solidity es un lenguaje de programación de alto nivel con una sintaxis similar a ECMAScript (javascript).
  • Se compila en el código de bytes de EVM, algo que solo el EVM puede entender.
  • El compilador se llama Solc.

Tomemos este simple contrato como ejemplo:

solidez del pragma ^ 0.5.0;

Ejemplo de contrato {

uint a = 10 + 5;

}

Simple como eso. Ahora compilémoslo. Si vamos a Detalles del contrato en Terminal podemos ver mucha información.

En este caso, el código compilado es:

0x6080604052600f600055348015601457600080fd5b5060358060226000396000f3fe6080604052600080fdfea165627a7a72305820bf75c57b7d8745a79baee513ead21a9eb8b075896d3c8e754c572959

Estos valores largos son una representación hexadecimal del contrato final, también conocido como bytecode. EVM solo entiende el código de bytes.

Pero, si algo sale mal, nos quedamos atascados con algún error, por ejemplo, no se puede depurar el bytecode.

Códigos de operación

El idioma por encima del código de bytes es el código de operación. Opcode es un lenguaje de programación de bajo nivel. Solidity y Opcode son como C y lenguaje ensamblador, por ejemplo.

Entonces, cuando necesitamos depurar alguna transacción fallida, depuramos el código de operación.

Una cosa que debe saber sobre Solidity y depuración: es muy difícil. Pero no es imposible, así que profundicemos.

Este es el código de operación de nuestro contrato de ejemplo:

0 PUSH1 60

02 PUSH1 40

04 MSTORE

05 PUSH1 0f

07 PUSH1 00

09 TIENDA

10 CALLVALUE

11 DUP1

12 ISZERO

13 PUSH1 14

15 JUMPI

16 PUSH1 00

18 DUP1

19 REVERTIR

20 JUMPDEST

21 POP

22 PUSH1 35

24 DUP1

25 PUSH1 22

27 PUSH1 00

29 CODECOPY

30 PUSH1 00

32 REGRESO

33 NO VÁLIDO

34 PUSH1 80

36 PUSH1 40

38 MSTORE

39 PUSH1 00

41 DUP1

42 REVERTIR

43 NO VÁLIDO

44 LOG1

45 PUSH6 627a7a723058

52 SHA3

53 INVALIDO

54 PUSH22 c57b7d8745a79baee513ead21a9eb8b075896f8e4c59

77 NO VÁLIDO

78 DUP10

79 Y

80 JUMPI

81 NO VÁLIDO

82 EQUILIBRIO

83 PUSH29 750029

Los códigos de operación son las instrucciones legibles por humanos de bajo nivel del programa. Todos los códigos de operación tienen sus contrapartes hexadecimales, p. Ej. MSTORE es 0x52.

El EVM es Stack Machine. Se basa en la estructura LIFO (Last In First Out). Para simplificar, imagina apilar rebanadas de pan en un microondas, la ÚLTIMA rebanada que pones es la PRIMERA que sacas.

En aritmética normal, escribimos nuestra ecuación de esta manera:

10 + 2 * 2

y la respuesta es 14, porque hacemos la multiplicación antes de la suma.

En una máquina apiladora, funciona según el principio LIFO:

2 2 * 10 +

Significa, ponga 2 en la pila primero, seguido de otro 2, luego seguido de la acción de multiplicación. El resultado es 4 sentados encima de la pila. Ahora agregue un número 10 encima del 4 y eventualmente sume los 2 números. El valor final de la pila se convierte en 14.

El acto de poner datos en la pila se llama instrucción PUSH y el acto de eliminar datos de la pila se llama instrucción POP. Es obvio que el código de operación más común que vemos en nuestro ejemplo anterior es PUSH1, que significa poner 1 byte de datos en la pila..

Entonces, esta instrucción:

PUSH1 0x60

significa poner un valor de 1 byte de “0x60” en la pila. Casualmente, el valor hexadecimal de PUSH1 también es “0x60”. Eliminando el “0x” no obligatorio, podríamos escribir esta lógica en bytecode como “6060”.

Vayamos un poco mas lejos.

PUSH1 0x60 PUSH1 0x40 MSTORE

El MSTORE (0x52) toma 2 entradas y no produce salida. Los códigos de operación anteriores significan:

PUSH1 (0x60): poner 0x60 en la pila.

PUSH1 (0x40): poner 0x40 en la pila.

MSTORE (0x52): asigne 0x60 de espacio de memoria y muévase a la posición 0x40.

El código de bytes resultante es:

6060604052

De hecho, siempre vemos este número mágico “6060604052” al comienzo de cualquier código de bytes de solidez porque es cómo arranca el contrato inteligente.

Para complicar aún más el asunto, 0x40 o 0x60 no se pueden interpretar como el número real 40 o 60. Como son hexadecimales, 40 en realidad equivale a 64 (16¹ x 4) y 60 equivale a 96 (16¹ x 6) en decimal.

En resumen, lo que está haciendo “PUSH1 0x60 PUSH1 0x40 MSTORE” es asignar 96 bytes de memoria y mover el puntero al comienzo del 64º byte. Ahora tenemos 64 bytes para espacio temporal y 32 bytes para almacenamiento de memoria temporal.

En el EVM, hay 3 lugares para almacenar datos. Primero, en la pila. Acabamos de utilizar el código de operación PUSH para almacenar datos allí como en el ejemplo anterior.

En segundo lugar en la memoria (RAM) donde usamos el código de operación MSTORE y por último, en el almacenamiento en disco donde usamos SSTORE para almacenar los datos. El gas necesario para almacenar datos en almacenamiento es el más caro y almacenar datos en apilar es el más barato.

Ahora es un buen momento para volver a nuestro código de Solidez de este tutorial y recapitular lo que aprendimos sobre la palabra reservada memoria y cómo el compilador nos obliga a especificar cómo almacenamos cadenas, por ejemplo.

Solo hemos cubierto los conceptos básicos del código de bytes y algunos códigos de operación..

No necesitamos conocer los códigos de operación para comenzar a escribir contratos inteligentes!

Por otro lado, el manejo de errores de EVM sigue siendo muy primitivo y es útil para ver los códigos de operación cuando las cosas van mal..

Conclusión

Esta primera lección tiene un poco más de teoría que de codificación real, pero es muy importante que los principiantes sepan cómo funcionan las cosas en Ethereum. En los próximos tutoriales, escribiremos un código más interesante y aprenderemos cómo implementar nuestro propio token en la cadena de bloques Ethereum..

Hasta entonces &# 128075;