Solidez - Guía rápida

Solidity es un lenguaje de programación de alto nivel orientado a contratos para implementar contratos inteligentes. Solidity está muy influenciado por C ++, Python y JavaScript y ha sido diseñado para apuntar a la máquina virtual Ethereum (EVM).

Solidity está tipado estáticamente, admite herencia, bibliotecas y lenguaje de programación de tipos complejos definidos por el usuario.

Puede utilizar Solidity para crear contratos para usos como votación, financiación colectiva, subastas ciegas y carteras con múltiples firmas.

¿Qué es Ethereum?

Ethereum es un ejemplo descentralizado. plataforma blockchain que ejecuta contratos inteligentes, es decir, aplicaciones que se ejecutan exactamente según lo programado sin ninguna posibilidad de tiempo de inactividad, censura, fraude o interferencia de terceros.

La máquina virtual Ethereum (EVM)

La máquina virtual Ethereum, también conocida como EVM, es el entorno de tiempo de ejecución para contratos inteligentes en Ethereum. La máquina virtual Ethereum se enfoca en brindar seguridad y ejecutar código no confiable en computadoras de todo el mundo.

El EVM se especializa en prevenir ataques de denegación de servicio y garantiza que los programas no tengan acceso al estado de los demás, lo que garantiza que la comunicación se pueda establecer sin ninguna interferencia potencial.

La máquina virtual Ethereum ha sido diseñada para servir como entorno de ejecución para contratos inteligentes basados ​​en Ethereum.

¿Qué es el contrato inteligente?

Un contrato inteligente es un protocolo informático destinado a facilitar, verificar o hacer cumplir digitalmente la negociación o el cumplimiento de un contrato. Los contratos inteligentes permiten la realización de transacciones creíbles sin terceros. Estas transacciones son rastreables e irreversibles.

El concepto de contratos inteligentes fue propuesto por primera vez por Nick Szabo en 1994. Szabo es un erudito legal y criptógrafo conocido por sentar las bases para la moneda digital.

Está bien si no comprende Smart Contract en este momento; entraremos en más detalles más adelante.

Este capítulo explica cómo podemos configurar el compilador Solidity en una máquina CentOS. Si no tiene una máquina Linux, puede usar nuestro compilador en línea para contratos pequeños y para aprender rápidamente Solidity.

Método 1: npm / Node.js

Esta es la forma más rápida de instalar el compilador Solidity en su máquina CentoS. Tenemos los siguientes pasos para instalar el compilador de solidez:

Instalar Node.js

Primero asegúrese de tener node.js disponible en su máquina CentOS. Si no está disponible, instálelo usando los siguientes comandos:

# First install epel-release
$sudo yum install epel-release

# Now install nodejs
$sudo yum install nodejs

# Next install npm (Nodejs Package Manager )
$sudo yum install npm

# Finally verify installation
$npm --version

Si todo se ha instalado, verá una salida similar a esta:

3.10.10

Instalar solc

Una vez que tenga instalado el administrador de paquetes Node.js, puede proceder a instalar el compilador Solidity como se muestra a continuación:

$sudonpm install -g solc

El comando anterior instalará el programa solcjs y lo hará disponible globalmente en todo el sistema. Ahora puede probar su compilador Solidity emitiendo el siguiente comando:

$solcjs-version

Si todo va bien, esto imprimirá algo de la siguiente manera:

0.5.2+commit.1df8f40c.Emscripten.clang

Ahora está listo para usar solcjs, que tiene menos características que el compilador estándar de Solidity, pero le dará un buen punto de partida.

Método 2: imagen de Docker

Puede extraer una imagen de Docker y comenzar a usarla para comenzar con la programación de Solidity. Los siguientes son los sencillos pasos. A continuación se muestra el comando para extraer una imagen de Docker de solidez.

$docker pull ethereum/solc:stable

Una vez que se descarga una imagen de la ventana acoplable, podemos verificarla usando el siguiente comando.

$docker run ethereum/solc:stable-version

Esto imprimirá algo de la siguiente manera:

$ docker run ethereum/solc:stable -version

solc, the solidity compiler commandlineinterfaceVersion: 0.5.2+commit.1df8f40c.Linux.g++

Método 3: instalación de paquetes binarios

Si está dispuesto a instalar el compilador completo en su máquina Linux, consulte el sitio web oficial Instalación del compilador de solidez.

Los archivos de origen de Solidity pueden contener cualquier número de definiciones de contrato, directivas de importación y directivas pragma.

Comencemos con un archivo fuente simple de Solidity. A continuación se muestra un ejemplo de un archivo Solidity:

pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
   uint storedData;
   function set(uint x) public {
      storedData = x;
   }
   function get() public view returns (uint) {
      return storedData;
   }
}

Pragma

La primera línea es una directiva pragma que indica que el código fuente está escrito para la versión 0.4.0 de Solidity o cualquier otra versión más reciente que no rompa la funcionalidad hasta la versión 0.6.0, pero sin incluirla.

Una directiva pragma siempre es local para un archivo de origen y si importa otro archivo, el pragma de ese archivo no se aplicará automáticamente al archivo de importación.

Entonces, un pragma para un archivo que no se compilará antes de la versión 0.4.0 y tampoco funcionará en un compilador a partir de la versión 0.5.0 se escribirá de la siguiente manera:

pragma solidity ^0.4.0;

Aquí la segunda condición se agrega usando ^.

Contrato

Un contrato de solidez es una colección de código (sus funciones) y datos (su estado) que reside en una dirección específica en Ethereumblockchain.

La línea uintstoredData declara una variable de estado llamada storedData de tipo uint y las funciones set y get se pueden usar para modificar o recuperar el valor de la variable.

Importación de archivos

Aunque el ejemplo anterior no tiene una declaración de importación, Solidity admite declaraciones de importación que son muy similares a las disponibles en JavaScript.

La siguiente declaración importa todos los símbolos globales de "nombre de archivo".

import "filename";

El siguiente ejemplo crea un nuevo símbolo global symbolName cuyos miembros son todos los símbolos globales de "filename".

import * as symbolName from "filename";

Para importar un archivo x desde el mismo directorio que el archivo actual, use importar "./x" como x ;. Si usa importar "x" como x; en su lugar, se podría hacer referencia a un archivo diferente en un "directorio de inclusión" global.

Palabras clave reservadas

Las siguientes son las palabras clave reservadas en Solidity:

resumen después alias aplicar
auto caso captura copia de
defecto definir final inmutable
implementos en en línea dejar
macro partido mudable nulo
de anular parcial promesa
referencia reubicable sellado tamaño de
estático apoya cambiar tratar
typedef tipo de desenfrenado

Estamos usando Remix IDE para compilar y ejecutar nuestra base de código de solidez.

Step 1 - Copie el código dado en Remix IDE Code Section.

Ejemplo

pragma solidity ^0.5.0;
contract SolidityTest {
   constructor() public{
   }
   function getResult() public view returns(uint){
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

Step 2 - En la pestaña Compilar, haga clic en Start to Compile botón.

Step 3 - En la pestaña Ejecutar, haga clic en Deploy botón.

Step 4 - En la pestaña Ejecutar, seleccione SolidityTest at 0x... en el menú desplegable.

Step 5 - Click getResult Botón para mostrar el resultado.

Salida

0: uint256: 3

Solidity admite comentarios de estilo C y C ++, por lo tanto:

  • Cualquier texto entre // y el final de una línea se trata como un comentario y Solidity Compiler lo ignora.

  • Cualquier texto entre los caracteres / * y * / se trata como un comentario. Esto puede abarcar varias líneas.

Ejemplo

El siguiente ejemplo muestra cómo usar comentarios en Solidity.

function getResult() public view returns(uint){
   // This is a comment. It is similar to comments in C++

   /*
      * This is a multi-line comment in solidity
      * It is very similar to comments in C Programming
   */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

Al escribir un programa en cualquier idioma, debe utilizar varias variables para almacenar información diversa. Las variables no son más que ubicaciones de memoria reservadas para almacenar valores. Esto significa que cuando crea una variable, reserva algo de espacio en la memoria.

Es posible que desee almacenar información de varios tipos de datos como carácter, carácter ancho, entero, punto flotante, punto flotante doble, booleano, etc. En función del tipo de datos de una variable, el sistema operativo asigna memoria y decide qué se puede almacenar en el memoria reservada.

Tipos de valor

Solidity ofrece al programador una amplia variedad de tipos de datos integrados y definidos por el usuario. La siguiente tabla enumera siete tipos de datos básicos de C ++:

Tipo Palabra clave Valores
Booleano bool verdadero Falso
Entero int / uint Enteros firmados y sin firmar de diferentes tamaños.
Entero int8 a int256 Firmado int de 8 bits a 256 bits. int256 es lo mismo que int.
Entero uint8 a uint256 Int sin signo de 8 bits a 256 bits. uint256 es lo mismo que uint.
Números de punto fijo fijo / no fijo Números de punto fijo firmados y sin firmar de diferentes tamaños.
Números de punto fijo fijo / no fijo Números de punto fijo firmados y sin firmar de diferentes tamaños.
Números de punto fijo FixedMxN Número de punto fijo con signo donde M representa el número de bits tomados por tipo y N representa los puntos decimales. M debe ser divisible entre 8 y va de 8 a 256. N puede ser de 0 a 80. Fixed es lo mismo que Fixed128x18.
Números de punto fijo ufixedMxN Número de punto fijo sin signo donde M representa el número de bits tomados por tipo y N representa los puntos decimales. M debe ser divisible por 8 y va de 8 a 256. N puede ser de 0 a 80. ufixed es lo mismo que ufixed128x18.

habla a

address contiene el valor de 20 bytes que representa el tamaño de una dirección Ethereum. Se puede usar una dirección para obtener el saldo usando el método .balance y se puede usar para transferir el saldo a otra dirección usando el método .transfer.

address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

La solidez admite tres tipos de variables.

  • State Variables - Variables cuyos valores se almacenan permanentemente en un almacén de contrato.

  • Local Variables - Variables cuyos valores están presentes hasta que se ejecuta la función.

  • Global Variables - Existen variables especiales en el espacio de nombres global utilizado para obtener información sobre la cadena de bloques.

La solidez es un lenguaje de tipo estático, lo que significa que el tipo de variable local o estatal debe especificarse durante la declaración. Cada variable declarada siempre tiene un valor predeterminado basado en su tipo. No existe el concepto de "indefinido" o "nulo".

Variable de estado

Variables cuyos valores se almacenan permanentemente en un almacenamiento de contrato.

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData;      // State variable
   constructor() public {
      storedData = 10;   // Using State variable
   }
}

Variable local

Variables cuyos valores están disponibles solo dentro de una función donde está definida. Los parámetros de la función son siempre locales para esa función.

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result; //access the local variable
   }
}

Ejemplo

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: uint256: 10

Variables globales

Estas son variables especiales que existen en el espacio de trabajo global y proporcionan información sobre la cadena de bloques y las propiedades de la transacción.

Nombre Devoluciones
blockhash (uint blockNumber) devuelve (bytes32) Hash del bloque dado: solo funciona para los 256 bloques más recientes, excluyendo los actuales
block.coinbase (dirección pagadera) Dirección actual del minero del bloque
block.dificultad (uint) Dificultad del bloque actual
block.gaslimit (uint) Límite de gas del bloque actual
block.number (uint) Número de bloque actual
block.timestamp (uint) Marca de tiempo del bloque actual como segundos desde la época de Unix
gasleft () devuelve (uint256) Gas restante
msg.data (bytes calldata) Datos de llamada completos
msg.sender (dirección pagadera) Remitente del mensaje (llamante actual)
msg.sig (bytes4) Primeros cuatro bytes de los datos de llamada (identificador de función)
msg.value (uint) Número de wei enviados con el mensaje
ahora (uint) Marca de tiempo del bloque actual
tx.gasprice (uint) Precio del gas de la transacción
tx.origin (dirección pagadera) Remitente de la transacción

Nombres de variables de solidez

Al nombrar sus variables en Solidity, tenga en cuenta las siguientes reglas.

  • No debe utilizar ninguna de las palabras clave reservadas de Solidity como nombre de variable. Estas palabras clave se mencionan en la siguiente sección. Por ejemplo, los nombres de variables booleanas o de ruptura no son válidos.

  • Los nombres de las variables de solidez no deben comenzar con un número (0-9). Deben comenzar con una letra o un carácter de subrayado. Por ejemplo, 123test es un nombre de variable no válido, pero _123test es válido.

  • Los nombres de las variables de solidez distinguen entre mayúsculas y minúsculas. Por ejemplo, Nombre y nombre son dos variables diferentes.

El alcance de las variables locales se limita a la función en la que se definen, pero las variables de estado pueden tener tres tipos de alcances.

  • Public- Se puede acceder a las variables de estado públicas tanto internamente como a través de mensajes. Para una variable de estado pública, se genera una función de obtención automática.

  • Internal - Se puede acceder a las variables de estado internas solo de forma interna desde el contrato actual o contrato derivado de él sin utilizar este.

  • Private - Se puede acceder a las variables de estado privadas solo internamente desde el contrato actual, no están definidas en el contrato derivado del mismo.

Ejemplo

pragma solidity ^0.5.0;
contract C {
   uint public data = 30;
   uint internal iData= 10;
   
   function x() public returns (uint) {
      data = 3; // internal access
      return data;
   }
}
contract Caller {
   C c = new C();
   function f() public view returns (uint) {
      return c.data(); //external access
   }
}
contract D is C {
   function y() public returns (uint) {
      iData = 3; // internal access
      return iData;
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

¿Qué es un operador?

Tomemos una expresión simple 4 + 5 is equal to 9. Aquí 4 y 5 se llamanoperands y '+' se llama operator. Solidity admite los siguientes tipos de operadores.

  • Operadores aritméticos
  • Operadores de comparación
  • Operadores lógicos (o relacionales)
  • Operadores de Asignación
  • Operadores condicionales (o ternarios)

Echemos un vistazo a todos los operadores uno por uno.

Operadores aritméticos

Solidity admite los siguientes operadores aritméticos:

Suponga que la variable A tiene 10 y la variable B tiene 20, entonces -

Mostrar ejemplo

No Señor. Operador y descripción
1

+ (Addition)

Agrega dos operandos

Ex: A + B dará 30

2

- (Subtraction)

Resta el segundo operando del primero

Ex: A - B dará -10

3

* (Multiplication)

Multiplica ambos operandos

Ex: A * B dará 200

4

/ (Division)

Divide el numerador entre el denominador

Ex: B / A dará 2

5

% (Modulus)

Emite el resto de una división entera

Ex: B% A dará 0

6

++ (Increment)

Aumenta un valor entero en uno

Ex: A ++ dará 11

7

-- (Decrement)

Disminuye un valor entero en uno.

Ex: A-- dará 9

Operadores de comparación

Solidity admite los siguientes operadores de comparación:

Suponga que la variable A tiene 10 y la variable B tiene 20, entonces -

Mostrar ejemplo

No Señor. Operador y descripción
1

= = (Equal)

Comprueba si el valor de dos operandos es igual o no, si es así, la condición se convierte en verdadera.

Ex: (A == B) no es cierto.

2

!= (Not Equal)

Comprueba si el valor de dos operandos es igual o no, si los valores no son iguales, la condición se vuelve verdadera.

Ex: (A! = B) es cierto.

3

> (Greater than)

Comprueba si el valor del operando izquierdo es mayor que el valor del operando derecho; si es así, la condición se cumple.

Ex: (A> B) no es cierto.

4

< (Less than)

Comprueba si el valor del operando izquierdo es menor que el valor del operando derecho; si es así, la condición se cumple.

Ex: (A <B) es cierto.

5

>= (Greater than or Equal to)

Comprueba si el valor del operando izquierdo es mayor o igual que el valor del operando derecho; si es así, la condición se cumple.

Ex: (A> = B) no es cierto.

6

<= (Less than or Equal to)

Comprueba si el valor del operando izquierdo es menor o igual que el valor del operando derecho; si es así, la condición se cumple.

Ex: (A <= B) es cierto.

Operadores logicos

Solidity admite los siguientes operadores lógicos:

Suponga que la variable A tiene 10 y la variable B tiene 20, entonces -

Mostrar ejemplo

No Señor. Operador y descripción
1

&& (Logical AND)

Si ambos operandos son distintos de cero, la condición se cumple.

Ex: (A && B) es cierto.

2

|| (Logical OR)

Si alguno de los dos operandos es distinto de cero, la condición se cumple.

Ex: (A || B) es cierto.

3

! (Logical NOT)

Invierte el estado lógico de su operando. Si una condición es verdadera, entonces el operador lógico NOT hará que sea falsa.

Ex:! (A && B) es falso.

Operadores bit a bit

Solidity admite los siguientes operadores bit a bit:

Suponga que la variable A tiene 2 y la variable B tiene 3, entonces -

Mostrar ejemplo

No Señor. Operador y descripción
1

& (Bitwise AND)

Realiza una operación booleana AND en cada bit de sus argumentos enteros.

Ex: (A y B) es 2.

2

| (BitWise OR)

Realiza una operación booleana OR en cada bit de sus argumentos enteros.

Ex: (A | B) es 3.

3

^ (Bitwise XOR)

Realiza una operación OR exclusiva booleana en cada bit de sus argumentos enteros. OR exclusivo significa que el operando uno es verdadero o el operando dos es verdadero, pero no ambos.

Ex: (A ^ B) es 1.

4

~ (Bitwise Not)

Es un operador unario y opera invirtiendo todos los bits del operando.

Ex: (~ B) es -4.

5

<< (Left Shift)

Mueve todos los bits de su primer operando a la izquierda el número de lugares especificado en el segundo operando. Los nuevos bits se llenan de ceros. Cambiar un valor a la izquierda en una posición equivale a multiplicarlo por 2, cambiar dos posiciones equivale a multiplicar por 4, y así sucesivamente.

Ex: (A << 1) es 4.

6

>> (Right Shift)

Operador de cambio a la derecha binario. El valor del operando izquierdo se mueve hacia la derecha el número de bits especificado por el operando derecho.

Ex: (A >> 1) es 1.

7

>>> (Right shift with Zero)

Este operador es como el operador >>, excepto que los bits desplazados hacia la izquierda son siempre cero.

Ex: (A >>> 1) es 1.

Operadores de Asignación

Solidity admite los siguientes operadores de asignación:

Mostrar ejemplo

No Señor. Operador y descripción
1

= (Simple Assignment )

Asigna valores del operando del lado derecho al operando del lado izquierdo

Ex: C = A + B asignará el valor de A + B a C

2

+= (Add and Assignment)

Agrega el operando derecho al operando izquierdo y asigna el resultado al operando izquierdo.

Ex: C + = A es equivalente a C = C + A

3

−= (Subtract and Assignment)

Resta el operando derecho del operando izquierdo y asigna el resultado al operando izquierdo.

Ex: C - = A es equivalente a C = C - A

4

*= (Multiply and Assignment)

Multiplica el operando derecho con el operando izquierdo y asigna el resultado al operando izquierdo.

Ex: C * = A es equivalente a C = C * A

5

/= (Divide and Assignment)

Divide el operando izquierdo con el operando derecho y asigna el resultado al operando izquierdo.

Ex: C / = A es equivalente a C = C / A

6

%= (Modules and Assignment)

Toma el módulo usando dos operandos y asigna el resultado al operando izquierdo.

Ex: C% = A es equivalente a C = C% A

Note - La misma lógica se aplica a los operadores bit a bit, por lo que se convertirán en << =, >> =, >> =, & =, | = y ^ =.

Operador condicional (? :)

El operador condicional primero evalúa una expresión para un valor verdadero o falso y luego ejecuta una de las dos declaraciones dadas dependiendo del resultado de la evaluación.

Mostrar ejemplo

No Señor. Operador y descripción
1

? : (Conditional )

¿Si la condición es verdadera? Entonces valor X: De lo contrario valor Y

Mientras redacta un contrato, puede encontrarse con una situación en la que necesite realizar una acción una y otra vez. En tales situaciones, necesitaría escribir declaraciones de bucle para reducir el número de líneas.

Solidity admite todos los bucles necesarios para aliviar la presión de la programación.

No Señor Bucles y descripción
1

Mientras bucle

El ciclo más básico de Solidity es el ciclo while, que se analizará en este capítulo.

2

hacer ... while Loop

El bucle do ... while es similar al bucle while excepto que la verificación de la condición ocurre al final del bucle.

3

En bucle

El bucle for es la forma más compacta de bucle. Incluye las siguientes tres partes importantes.

4

Control de bucle

Solidity proporciona un control total para manejar bucles y cambiar declaraciones.

Al escribir un programa, puede haber una situación en la que necesite adoptar uno de un conjunto determinado de rutas. En tales casos, debe utilizar declaraciones condicionales que permitan a su programa tomar decisiones correctas y realizar acciones correctas.

Solidity admite declaraciones condicionales que se utilizan para realizar diferentes acciones basadas en diferentes condiciones. Aquí explicaremos elif..else declaración.

Diagrama de flujo de if-else

El siguiente diagrama de flujo muestra cómo funciona la instrucción if-else.

La solidez admite las siguientes formas de if..else declaración -

No Señor Declaraciones y descripción
1

si declaración

La declaración if es la declaración de control fundamental que permite a Solidity tomar decisiones y ejecutar declaraciones de forma condicional.

2

declaración if ... else

La declaración 'if ... else' es la siguiente forma de declaración de control que permite a Solidity ejecutar declaraciones de una manera más controlada.

3

if ... else if ... declaración.

La declaración if ... else if ... es una forma avanzada de if ... else que permite a Solidity tomar una decisión correcta a partir de varias condiciones.

Solidity admite el literal de cadena con comillas dobles (") y comillas simples ('). Proporciona cadena como tipo de datos para declarar una variable de tipo Cadena.

pragma solidity ^0.5.0;

contract SolidityTest {
   string data = "test";
}

En el ejemplo anterior, "prueba" es un literal de cadena y los datos son una variable de cadena. La forma más preferida es usar tipos de bytes en lugar de String, ya que la operación de string requiere más gas en comparación con la operación de bytes. Solidity proporciona conversión incorporada entre bytes a cadena y viceversa. En Solidity podemos asignar un literal de cadena a una variable de tipo byte32 fácilmente. Solidity lo considera como un byte32 literal.

pragma solidity ^0.5.0;

contract SolidityTest {
   bytes32 data = "test";
}

Personajes de escape

No Señor. Descripción del personaje
1

\n

Inicia una nueva línea.

2

\\

Barra invertida

3

\'

Una frase

4

\"

Cotización doble

5

\b

Retroceso

6

\f

Alimentación de formulario

7

\r

Retorno de carro

8

\t

Lengüeta

9

\v

Ficha vertical

10

\xNN

Representa el valor hexadecimal e inserta los bytes correspondientes.

11

\uNNNN

Representa el valor Unicode e inserta la secuencia UTF-8.

Conversión de bytes a cadenas

Los bytes se pueden convertir a String usando el constructor string ().

bytes memory bstr = new bytes(10);
string message = string(bstr);

Ejemplo

Pruebe el siguiente código para comprender cómo funciona la cadena en Solidity.

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: string: 3

Array es una estructura de datos que almacena una colección secuencial de tamaño fijo de elementos del mismo tipo. Una matriz se utiliza para almacenar una colección de datos, pero a menudo es más útil pensar en una matriz como una colección de variables del mismo tipo.

En lugar de declarar variables individuales, como número0, número1, ... y número99, declaras una variable de matriz como números y usas números [0], números [1] y ..., números [99] para representar variables individuales. Se accede a un elemento específico de una matriz mediante un índice.

En Solidity, una matriz puede ser de tamaño fijo en tiempo de compilación o de tamaño dinámico. Para la matriz de almacenamiento, también puede tener diferentes tipos de elementos. En el caso de una matriz de memoria, el tipo de elemento no se puede mapear y en caso de que se use como parámetro de función, el tipo de elemento debe ser un tipo ABI.

Todas las matrices constan de ubicaciones de memoria contiguas. La dirección más baja corresponde al primer elemento y la dirección más alta al último elemento.

Declaración de matrices

Para declarar una matriz de tamaño fijo en Solidity, el programador especifica el tipo de elementos y el número de elementos requeridos por una matriz de la siguiente manera:

type arrayName [ arraySize ];

Esto se llama matriz unidimensional. losarraySize debe ser una constante entera mayor que cero y typepuede ser cualquier tipo de datos de solidez válido. Por ejemplo, para declarar una matriz de 10 elementos llamada balance de tipo uint, use esta declaración:

uint balance[10];

Para declarar una matriz de tamaño dinámico en Solidity, el programador especifica el tipo de los elementos de la siguiente manera:

type[] arrayName;

Inicialización de matrices

Puede inicializar los elementos de la matriz Solidity uno por uno o usando una sola declaración de la siguiente manera:

uint balance[3] = [1, 2, 3];

El número de valores entre corchetes [] no puede ser mayor que el número de elementos que declaramos para el arreglo entre corchetes []. A continuación se muestra un ejemplo para asignar un solo elemento de la matriz:

Si omite el tamaño de la matriz, se crea una matriz lo suficientemente grande como para contener la inicialización. Por lo tanto, si escribe -

uint balance[] = [1, 2, 3];

Creará exactamente la misma matriz que hizo en el ejemplo anterior.

balance[2] = 5;

Lo anterior cesionarios declaración elemento número 3 rd en la matriz de un valor de 5.

Crear matrices de memoria dinámica

Las matrices de memoria dinámica se crean utilizando una nueva palabra clave.

uint size = 3;
uint balance[] = new uint[](size);

Acceso a elementos de matriz

Se accede a un elemento indexando el nombre de la matriz. Esto se hace colocando el índice del elemento entre corchetes después del nombre de la matriz. Por ejemplo

uint salary = balance[2];

La declaración anterior se llevará a 3 rd elemento de la matriz y asignar el valor a la variable salario. A continuación se muestra un ejemplo, que utilizará los tres conceptos mencionados anteriormente, a saber. declaración, asignación y acceso a matrices -

Miembros

  • length- length devuelve el tamaño de la matriz. length se puede usar para cambiar el tamaño de la matriz dinámica al configurarlo.

  • push- push permite agregar un elemento a una matriz de almacenamiento dinámica al final. Devuelve la nueva longitud de la matriz.

Ejemplo

Pruebe el siguiente código para comprender cómo funcionan las matrices en Solidity.

pragma solidity ^0.5.0;

contract test {
   function testArray() public pure{
      uint len = 7; 
      
      //dynamic array
      uint[] memory a = new uint[](7);
      
      //bytes is same as byte[]
      bytes memory b = new bytes(len);
      
      assert(a.length == 7);
      assert(b.length == len);
      
      //access array variable
      a[6] = 8;
      
      //test array variable
      assert(a[6] == 8);
      
      //static array
      uint[3] memory c = [uint(1) , 2, 3];
      assert(c.length == 3);
   }
}

Las enumeraciones restringen una variable para tener uno de los pocos valores predefinidos. Los valores de esta lista enumerada se denominan enumeraciones.

Con el uso de enumeraciones es posible reducir la cantidad de errores en su código.

Por ejemplo, si consideramos una solicitud para una tienda de jugos frescos, sería posible restringir el tamaño del vaso a pequeño, mediano y grande. Esto aseguraría que no permitiría a nadie pedir ningún tamaño que no sea pequeño, mediano o grande.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona la enumeración en Solidity.

pragma solidity ^0.5.0;

contract test {
   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
   FreshJuiceSize choice;
   FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;

   function setLarge() public {
      choice = FreshJuiceSize.LARGE;
   }
   function getChoice() public view returns (FreshJuiceSize) {
      return choice;
   }
   function getDefaultChoice() public pure returns (uint) {
      return uint(defaultChoice);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Primer clic setLarge Botón para establecer el valor como GRANDE y luego haga clic en getChoice para obtener la opción seleccionada.

Salida

uint8: 2

Hacer clic getDefaultChoice Botón para obtener la opción predeterminada.

Salida

uint256: 1

Los tipos de estructura se utilizan para representar un registro. Suponga que desea realizar un seguimiento de sus libros en una biblioteca. Es posible que desee realizar un seguimiento de los siguientes atributos sobre cada libro:

  • Title
  • Author
  • Subject
  • ID del libro

Definición de una estructura

Para definir una estructura, debe utilizar el structpalabra clave. La palabra clave struct define un nuevo tipo de datos, con más de un miembro. El formato de la declaración de estructura es el siguiente:

struct struct_name { 
   type1 type_name_1;
   type2 type_name_2;
   type3 type_name_3;
}

Ejemplo

struct Book { 
   string title;
   string author;
   uint book_id;
}

Accediendo a una estructura y su variable

Para acceder a cualquier miembro de una estructura, usamos el operador de acceso a miembros (.). El operador de acceso a miembros se codifica como un período entre el nombre de la variable de estructura y el miembro de estructura al que deseamos acceder. Usaría la estructura para definir variables de tipo de estructura. El siguiente ejemplo muestra cómo utilizar una estructura en un programa.

Ejemplo

Pruebe el siguiente código para comprender cómo funcionan las estructuras en Solidity.

pragma solidity ^0.5.0;

contract test {
   struct Book { 
      string title;
      string author;
      uint book_id;
   }
   Book book;

   function setBook() public {
      book = Book('Learn Java', 'TP', 1);
   }
   function getBookId() public view returns (uint) {
      return book.book_id;
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Primer clic setBook Botón para establecer el valor como GRANDE y luego haga clic en getBookId para obtener la identificación del libro seleccionado.

Salida

uint256: 1

El mapeo es un tipo de referencia como matrices y estructuras. A continuación se muestra la sintaxis para declarar un tipo de mapeo.

mapping(_KeyType => _ValueType)

Dónde

  • _KeyType- puede ser cualquier tipo incorporado más bytes y cadenas. No se permite ningún tipo de referencia ni objetos complejos.

  • _ValueType - puede ser de cualquier tipo.

Consideraciones

  • El mapeo solo puede tener el tipo de storage y se utilizan generalmente para variables de estado.

  • El mapeo se puede marcar como público. La solidez crea automáticamente un captador para él.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona el tipo de mapeo en Solidity.

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Primer clic updateBalance Botón para establecer el valor como 10 y luego mire en los registros que mostrarán la salida decodificada como -

Salida

{
   "0": "uint256: 10"
}

La solidez permite la conversión tanto implícita como explícita. El compilador de solidez permite la conversión implícita entre dos tipos de datos siempre que no sea posible una conversión implícita y no haya pérdida de información. Por ejemplo, uint8 es convertible a uint16 pero int8 es convertible a uint256 ya que int8 puede contener un valor negativo no permitido en uint256.

Conversión explícita

Podemos convertir explícitamente un tipo de datos en otro usando la sintaxis del constructor.

int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == two complement representation of -3 in 256 bit format.

La conversión a tipos más pequeños cuesta bits de orden superior.

uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678

La conversión a un tipo superior agrega bits de relleno a la izquierda.

uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234

La conversión a bytes más pequeños cuesta datos de orden superior.

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12

La conversión a un byte más grande agrega bits de relleno a la derecha.

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000

La conversión entre bytes de tamaño fijo e int solo es posible cuando ambos son del mismo tamaño.

bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12

Los números hexadecimales se pueden asignar a cualquier tipo de entero si no se necesita truncamiento.

uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, as truncation required to 0x3456

En solidez podemos usar wei, finney, szabo o ether como sufijo de un literal que se utilizará para convertir varias denominaciones basadas en éter. La unidad más baja es wei y 1e12 representa 1 x 10 12 .

assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);

Unidades de tiempo

Similar a la moneda, Solidity tiene unidades de tiempo donde la unidad más baja es el segundo y podemos usar segundos, minutos, horas, días y semanas como sufijo para indicar el tiempo.

assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);

Las variables especiales son variables disponibles globalmente y proporcionan información sobre la cadena de bloques. A continuación se muestra la lista de variables especiales:

No Señor. Variable especial y descripción
1

blockhash(uint blockNumber) returns (bytes32)

Hash del bloque dado: solo funciona para los 256 bloques más recientes, excluyendo los actuales.

2

block.coinbase (address payable)

Dirección actual del minero del bloque.

3

block.difficulty (uint)

dificultad del bloque actual.

4

block.gaslimit (uint)

Límite de gas del bloque actual.

5

block.number (uint)

Número de bloque actual.

6

block.timestamp

Marca de tiempo del bloque actual como segundos desde la época de Unix.

7

gasleft() returns (uint256)

Gas restante.

8

msg.data (bytes calldata)

Datos de llamada completos.

9

msg.sender (address payable)

Remitente del mensaje (llamada actual).

10

msg.sig (bytes4)

Primeros cuatro bytes de los datos de llamada (es decir, identificador de función)

11

msg.value (uint)

Número de wei enviados con el mensaje.

12

now (uint)

Marca de tiempo del bloque actual (alias de block.timestamp).

13

tx.gasprice (uint)

Precio del gas de la transacción.

14

tx.origin (address payable)

Remitente de la transacción (cadena de llamadas completa).

Ejemplo

Pruebe el siguiente código para ver el uso de msg, una variable especial para obtener la dirección del remitente en Solidity.

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Primer clic updateBalance Botón para establecer el valor como 10 y luego mire en los registros que mostrarán la salida decodificada como -

Salida

{
   "0": "uint256: 10"
}

La guía de estilo ayuda a mantener el diseño del código coherente y hacer que el código sea más legible. A continuación se presentan las mejores prácticas que se siguen al escribir contratos con Solidity.

Diseño de código

  • Indentation- Utilice 4 espacios en lugar de tabulación para mantener el nivel de sangría. Evite mezclar espacios con pestañas.

  • Two Blank Lines Rule - Utilice 2 líneas en blanco entre dos definiciones de contrato.

pragma solidity ^0.5.0;

contract LedgerBalance {
   //...
}
contract Updater {
   //...
}
  • One Blank Line Rule- Utilice 1 línea en blanco entre dos funciones. En caso de declaración única, no es necesario tener líneas en blanco.

pragma solidity ^0.5.0;

contract A {
   function balance() public pure;
   function account() public pure;
}
contract B is A {
   function balance() public pure {
      // ...
   }
   function account() public pure {
      // ...
   }
}
  • Maximum Line Length - Una sola línea no debe cruzar 79 caracteres para que los lectores puedan analizar fácilmente el código.

  • Wrapping rules- El primer argumento debe estar en una nueva línea sin abrir paréntesis. Utilice una sola sangría por argumento. Elemento de terminación); debería ser el último.

function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
variable = function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
event multipleArguments(
   address sender,
   address recipient,
   uint256 publicKey,
   uint256 amount,
   bytes32[] options
);
MultipleArguments(
   sender,
   recipient,
   publicKey,
   amount,
   options
);
  • Source Code Encoding - Preferiblemente se utilizará codificación UTF-8 o ASCII.

  • Imports - Las declaraciones de importación deben colocarse en la parte superior del archivo, justo después de la declaración pragma.

  • Order of Functions - Las funciones deben agruparse según su visibilidad.

pragma solidity ^0.5.0;

contract A {
   constructor() public {
      // ...
   }
   function() external {
      // ...
   }

   // External functions
   // ...

   // External view functions
   // ...

   // External pure functions 
   // ...

   // Public functions
   // ...

   // Internal functions
   // ...

   // Private functions
   // ...
}
  • Avoid extra whitespaces - Evite los espacios en blanco inmediatamente entre paréntesis, corchetes o llaves.

  • Control structures- Las llaves deben abrirse en la misma línea que la declaración. Cierre en su propia línea manteniendo la misma sangría. Utilice un espacio con abrazadera de apertura.

pragma solidity ^0.5.0;

contract Coin {
   struct Bank {
      address owner;
      uint balance;
   }
}
if (x < 3) {
   x += 1;
} else if (x > 7) {
   x -= 1;
} else {
   x = 5;
}
if (x < 3)
   x += 1;
else
   x -= 1;
  • Function Declaration- Utilice la regla anterior para llaves. Siempre agregue una etiqueta de visibilidad. La etiqueta de visibilidad debe ir primero antes de cualquier modificador personalizado.

function kill() public onlyowner {
   selfdestruct(owner);
}
  • Mappings - Evite los espacios en blanco al declarar variables de mapeo.

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
  • Variable declaration - Evite los espacios en blanco al declarar variables de matriz.

uint[] x;  // not unit [] x;
  • String declaration - Utilice comillas dobles para declarar una cadena en lugar de comillas simples.

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

Orden de diseño

Los elementos deben estar distribuidos en el siguiente orden.

  • Declaraciones de pragma

  • Importar declaraciones

  • Interfaces

  • Libraries

  • Contracts

Dentro de las interfaces, bibliotecas o contratos, el orden debe ser el siguiente:

  • Declaraciones de tipo

  • Variables de estado

  • Events

  • Functions

Convenciones de nombres

  • El contrato y la biblioteca se deben nombrar usando CapWords Style. Por ejemplo, SmartContract, Owner, etc.

  • El contrato y el nombre de la biblioteca deben coincidir con sus nombres de archivo.

  • En caso de múltiples contratos / bibliotecas en un archivo, use el nombre del contrato principal / biblioteca.

Own.sol

pragma solidity ^0.5.0;

// Owned.sol
contract Owned {
   address public owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      //....
   }
   function transferOwnership(address newOwner) public onlyOwner {
      //...
   }
}

Congress.sol

pragma solidity ^0.5.0;

// Congress.sol
import "./Owned.sol";

contract Congress is Owned, TokenRecipient {
   //...
}
  • Nombres de estructuras

    - Utilice el estilo de CapWords como SmartCoin.

  • Nombres de eventos

    - Utilice el estilo de CapWords como Depósito, AfterTransfer.

  • Nombres de funciones

    - Utilice MixedCase Style como initiateSupply.

  • Variables locales y estatales

    - Utilice MixedCase Style como creatorAddress, suministro.

  • Constantes

    - Utilice todas las letras mayúsculas con subrayado para separar palabras como MAX_BLOCKS.

  • Nombres de modificadores

    - Utilice mixCase Style como onlyAfter.

  • Nombres de enumeración

    - Utilice el estilo de CapWords como TokenGroup.

Una función es un grupo de código reutilizable que se puede llamar en cualquier parte de su programa. Esto elimina la necesidad de escribir el mismo código una y otra vez. Ayuda a los programadores a escribir códigos modulares. Las funciones permiten al programador dividir un programa grande en varias funciones pequeñas y manejables.

Como cualquier otro lenguaje de programación avanzado, Solidity también admite todas las características necesarias para escribir código modular usando funciones. Esta sección explica cómo escribir sus propias funciones en Solidity.

Definición de función

Antes de usar una función, necesitamos definirla. La forma más común de definir una función en Solidity es usando elfunction palabra clave, seguida de un nombre de función único, una lista de parámetros (que pueden estar vacíos) y un bloque de instrucciones rodeado de llaves.

Sintaxis

La sintaxis básica se muestra aquí.

function function-name(parameter-list) scope returns() {
   //statements
}

Ejemplo

Pruebe el siguiente ejemplo. Define una función llamada getResult que no toma parámetros -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

Llamar a una función

Para invocar una función más adelante en el Contrato, simplemente necesitaría escribir el nombre de esa función como se muestra en el siguiente código.

Pruebe el siguiente código para comprender cómo funciona la cadena en Solidity.

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: string: 3

Parámetros de función

Hasta ahora, hemos visto funciones sin parámetros. Pero existe la posibilidad de pasar diferentes parámetros al llamar a una función. Estos parámetros pasados ​​se pueden capturar dentro de la función y cualquier manipulación se puede realizar sobre esos parámetros. Una función puede tomar varios parámetros separados por comas.

Ejemplo

Pruebe el siguiente ejemplo. Hemos utilizado unuint2strfunción aquí. Toma un parámetro.

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: string: 3

La declaración de devolución

Una función de solidez puede tener una returndeclaración. Esto es necesario si desea devolver un valor de una función. Esta declaración debe ser la última declaración de una función.

Como en el ejemplo anterior, estamos usando la función uint2str para devolver una cadena.

En Solidity, una función también puede devolver varios valores. Vea el ejemplo a continuación:

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b;
  
      //alternative return statement to return 
      //multiple values
      //return(a*b, a+b);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: uint256: product 2
1: uint256: sum 3

Los modificadores de función se utilizan para modificar el comportamiento de una función. Por ejemplo, para agregar un requisito previo a una función.

Primero creamos un modificador con o sin parámetro.

contract Owner {
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

El cuerpo de la función se inserta donde el símbolo especial "_;" aparece en la definición de un modificador. Entonces, si se cumple la condición del modificador al llamar a esta función, la función se ejecuta y, de lo contrario, se lanza una excepción.

Vea el ejemplo a continuación:

pragma solidity ^0.5.0;

contract Owner {
   address owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}
contract Register is Owner {
   mapping (address => bool) registeredAddresses;
   uint price;
   constructor(uint initialPrice) public { price = initialPrice; }
   
   function register() public payable costs(price) {
      registeredAddresses[msg.sender] = true;
   }
   function changePrice(uint _price) public onlyOwner {
      price = _price;
   }
}

Las funciones de visualización garantizan que no modificarán el estado. Una función puede declararse comoview. Las siguientes declaraciones, si están presentes en la función, se consideran modificando el estado y el compilador arrojará una advertencia en tales casos.

  • Modificación de variables de estado.

  • Emitiendo eventos.

  • Creación de otros contratos.

  • Usando autodestrucción.

  • Envío de Ether a través de llamadas.

  • Llamar a cualquier función que no esté marcada como vista o pura.

  • Usando llamadas de bajo nivel.

  • Usando ensamblaje en línea que contiene ciertos códigos de operación.

Los métodos getter son funciones de vista predeterminadas.

Vea el siguiente ejemplo usando una función de visualización.

Ejemplo

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: uint256: product 2
1: uint256: sum 3

Las funciones puras aseguran que no lean ni modifiquen el estado. Una función puede declararse comopure. Las siguientes declaraciones, si están presentes en la función, se consideran como lectura del estado y el compilador arrojará una advertencia en tales casos.

  • Lectura de variables de estado.

  • Accediendo a la dirección (this) .balance o <address> .balance.

  • Accediendo a cualquiera de las variables especiales de block, tx, msg (se pueden leer msg.sig y msg.data).

  • Llamar a cualquier función que no esté marcada como pura.

  • Usando ensamblaje en línea que contiene ciertos códigos de operación.

Las funciones puras pueden usar las funciones revert () y require () para revertir posibles cambios de estado si ocurre un error.

Vea el siguiente ejemplo usando una función de visualización.

Ejemplo

pragma solidity ^0.5.0;

contract Test {
   function getResult() public pure returns(uint product, uint sum){
      uint a = 1; 
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: uint256: product 2
1: uint256: sum 3

La función de reserva es una función especial disponible para un contrato. Tiene las siguientes características:

  • Se llama cuando se llama a una función inexistente en el contrato.

  • Se requiere que esté marcado como externo.

  • No tiene nombre.

  • No tiene argumentos

  • No puede devolver nada.

  • Se puede definir uno por contrato.

  • Si no se marca como pagadero, arrojará una excepción si el contrato recibe ether sin datos.

El siguiente ejemplo muestra el concepto de una función de reserva por contrato.

Ejemplo

pragma solidity ^0.5.0;

contract Test {
   uint public x ;
   function() external { x = 1; }    
}
contract Sink {
   function() external payable { }
}
contract Caller {
   function callTest(Test test) public returns (bool) {
      (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
      require(success);
      // test.x is now 1

      address payable testPayable = address(uint160(address(test)));

      // Sending ether to Test contract,
      // the transfer will fail, i.e. this returns false here.
      return (testPayable.send(2 ether));
   }
   function callSink(Sink sink) public returns (bool) {
      address payable sinkPayable = address(sink);
      return (sinkPayable.send(2 ether));
   }
}

Puede tener varias definiciones para el mismo nombre de función en el mismo ámbito. La definición de la función debe diferir entre sí por los tipos y / o el número de argumentos en la lista de argumentos. No puede sobrecargar declaraciones de funciones que difieran solo por tipo de retorno.

El siguiente ejemplo muestra el concepto de sobrecarga de una función en Solidity.

Ejemplo

pragma solidity ^0.5.0;

contract Test {
   function getSum(uint a, uint b) public pure returns(uint){      
      return a + b;
   }
   function getSum(uint a, uint b, uint c) public pure returns(uint){      
      return a + b + c;
   }
   function callSumWithTwoArguments() public pure returns(uint){
      return getSum(1,2);
   }
   function callSumWithThreeArguments() public pure returns(uint){
      return getSum(1,2,3);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Haga clic en el botón callSumWithTwoArguments primero y luego en el botón callSumWithThreeArguments para ver el resultado.

Salida

0: uint256: 3
0: uint256: 6

Solidity también proporciona funciones matemáticas integradas. Los siguientes son métodos muy utilizados:

  • addmod(uint x, uint y, uint k) returns (uint)- calcula (x + y)% k donde la suma se realiza con precisión arbitraria y no se ajusta a 2 256 .

  • mulmod(uint x, uint y, uint k) returns (uint)- calcula (x * y)% k donde la suma se realiza con precisión arbitraria y no se ajusta a 2 256 .

El siguiente ejemplo muestra el uso de funciones matemáticas en Solidity.

Ejemplo

pragma solidity ^0.5.0;

contract Test {   
   function callAddMod() public pure returns(uint){
      return addmod(4, 5, 3);
   }
   function callMulMod() public pure returns(uint){
      return mulmod(4, 5, 3);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Haga clic en el botón callAddMod primero y luego en el botón callMulMod para ver el resultado.

Salida

0: uint256: 0
0: uint256: 2

Solidity también proporciona funciones criptográficas incorporadas. Los siguientes son métodos importantes:

  • keccak256(bytes memory) returns (bytes32) - calcula el hash Keccak-256 de la entrada.

  • sha256(bytes memory) returns (bytes32) - calcula el hash SHA-256 de la entrada.

  • ripemd160(bytes memory) returns (bytes20) - calcular el hash RIPEMD-160 de la entrada.

  • sha256(bytes memory) returns (bytes32) - calcula el hash SHA-256 de la entrada.

  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)- recuperar la dirección asociada con la clave pública de la firma de la curva elíptica o devolver cero en caso de error. Los parámetros de la función corresponden a los valores ECDSA de la firma: r - primeros 32 bytes de la firma; s: segundos 32 bytes de firma; v: último byte de firma. Este método devuelve una dirección.

El siguiente ejemplo muestra el uso de la función criptográfica en Solidity.

Ejemplo

pragma solidity ^0.5.0;

contract Test {   
   function callKeccak256() public pure returns(bytes32 result){
      return keccak256("ABC");
   }  
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8

El patrón de retiro garantiza que no se realice una llamada de transferencia directa, lo que representa una amenaza para la seguridad. El siguiente contrato muestra el uso inseguro de la transferencia de llamadas para enviar ether.

pragma solidity ^0.5.0;

contract Test {
   address payable public richest;
   uint public mostSent;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         // Insecure practice
         richest.transfer(msg.value);
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
}

El contrato anterior se puede convertir en inutilizable haciendo que el más rico sea un contrato de función de respaldo fallida. Cuando la función de reserva falla, la función BecomeRichest () también falla y el contrato se bloqueará para siempre. Para mitigar este problema, podemos utilizar el patrón de retirada.

En el patrón de retiro, restableceremos el monto pendiente antes de cada transferencia. Se asegurará de que solo falle el contrato de la persona que llama.

pragma solidity ^0.5.0;

contract Test {
   address public richest;
   uint public mostSent;

   mapping (address => uint) pendingWithdrawals;

   constructor() public payable {
      richest = msg.sender;
      mostSent = msg.value;
   }
   function becomeRichest() public payable returns (bool) {
      if (msg.value > mostSent) {
         pendingWithdrawals[richest] += msg.value;
         richest = msg.sender;
         mostSent = msg.value;
         return true;
      } else {
         return false;
      }
   }
   function withdraw() public {
      uint amount = pendingWithdrawals[msg.sender];
      pendingWithdrawals[msg.sender] = 0;
      msg.sender.transfer(amount);
   }
}

El acceso restringido a un contrato es una práctica común. De forma predeterminada, un estado de contrato es de solo lectura a menos que se especifique como público.

Podemos restringir quién puede modificar el estado del contrato o llamar a las funciones de un contrato usando modificadores. Crearemos y usaremos múltiples modificadores como se explica a continuación:

  • onlyBy - una vez utilizado en una función, solo el llamador mencionado puede llamar a esta función.

  • onlyAfter - una vez que se utiliza en una función, esa función se puede llamar después de cierto período de tiempo.

  • costs - una vez utilizado en una función, la persona que llama puede llamar a esta función solo si se proporciona cierto valor.

Ejemplo

pragma solidity ^0.5.0;

contract Test {
   address public owner = msg.sender;
   uint public creationTime = now;

   modifier onlyBy(address _account) {
      require(
         msg.sender == _account,
         "Sender not authorized."
      );
      _;
   }
   function changeOwner(address _newOwner) public onlyBy(owner) {
      owner = _newOwner;
   }
   modifier onlyAfter(uint _time) {
      require(
         now >= _time,
         "Function called too early."
      );
      _;
   }
   function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
      delete owner;
   }
   modifier costs(uint _amount) {
      require(
         msg.value >= _amount,
         "Not enough Ether provided."
      );
      _;
      if (msg.value > _amount)
         msg.sender.transfer(msg.value - _amount);
   }
   function forceOwnerChange(address _newOwner) public payable costs(200 ether) {
      owner = _newOwner;
      if (uint(owner) & 0 == 1) return;        
   }
}

Contract in Solidity es similar a una clase en C ++. Un contrato tiene las siguientes propiedades.

  • Constructor - Una función especial declarada con la palabra clave constructor que se ejecutará una vez por contrato y se invoca cuando se crea un contrato.

  • State Variables - Variables por Contrato para almacenar el estado del contrato.

  • Functions - Funciones por Contrato que pueden modificar las variables de estado para alterar el estado de un contrato.

Cuantificadores de visibilidad

A continuación se muestran varios cuantificadores de visibilidad para funciones / variables de estado de un contrato.

  • external- Las funciones externas están destinadas a ser llamadas por otros contratos. No se pueden utilizar para llamadas internas. Para llamar a una función externa dentro del contrato, se requiere la llamada this.function_name (). Las variables de estado no se pueden marcar como externas.

  • public- Las funciones públicas / variables se pueden utilizar tanto de forma externa como interna. Para la variable de estado pública, Solidity crea automáticamente una función getter.

  • internal - Las funciones internas / variables solo se pueden utilizar internamente o mediante contratos derivados.

  • private - Funciones / Variables privadas solo se pueden utilizar internamente y ni siquiera mediante contratos derivados.

Ejemplo

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//External Contract
contract D {
   function readData() public returns(uint) {
      C c = new C();
      c.updateData(7);         
      return c.getData();
   }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity. Ejecute varios métodos de contratos. Para E.getComputedResult () seguido de E.getResult () muestra -

Salida

0: uint256: 8

La herencia es una forma de ampliar la funcionalidad de un contrato. Solidity admite tanto la herencia única como la múltiple. A continuación se muestran los aspectos más destacados.

  • Un contrato derivado puede acceder a todos los miembros no privados, incluidos los métodos internos y las variables de estado. Pero usar esto no está permitido.

  • Se permite la sustitución de funciones siempre que la firma de la función siga siendo la misma. En caso de diferencia de parámetros de salida, la compilación fallará.

  • Podemos llamar a la función de un supercontrato usando una súper palabra clave o usando un nombre de súper contrato.

  • En caso de herencia múltiple, la llamada de función usando super da preferencia a la mayoría de los contratos derivados.

Ejemplo

pragma solidity ^0.5.0;

contract C {
   //private state variable
   uint private data;
   
   //public state variable
   uint public info;

   //constructor
   constructor() public {
      info = 10;
   }
   //private function
   function increment(uint a) private pure returns(uint) { return a + 1; }
   
   //public function
   function updateData(uint a) public { data = a; }
   function getData() public view returns(uint) { return data; }
   function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//Derived Contract
contract E is C {
   uint private result;
   C private c;
   constructor() public {
      c = new C();
   }  
   function getComputedResult() public {      
      result = compute(3, 5); 
   }
   function getResult() public view returns(uint) { return result; }
   function getData() public view returns(uint) { return c.info(); }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity. Ejecute varios métodos de contratos. Para E.getComputedResult () seguido de E.getResult () muestra -

Salida

0: uint256: 8

Constructor es una función especial declarada usando constructorpalabra clave. Es una función opcional y se utiliza para inicializar las variables de estado de un contrato. Las siguientes son las características clave de un constructor.

  • Un contrato solo puede tener un constructor.

  • Un código de constructor se ejecuta una vez cuando se crea un contrato y se usa para inicializar el estado del contrato.

  • Después de que se ejecuta un código de constructor, el código final se implementa en blockchain. Este código incluye funciones públicas y código accesible a través de funciones públicas. El código de constructor o cualquier método interno utilizado solo por el constructor no se incluyen en el código final.

  • Un constructor puede ser público o interno.

  • Un constructor interno marca el contrato como abstracto.

  • En caso de que no se defina ningún constructor, un constructor predeterminado está presente en el contrato.

pragma solidity ^0.5.0;

contract Test {
   constructor() public {}
}
  • En caso de que el contrato base tenga un constructor con argumentos, cada contrato derivado debe pasarlos.

  • El constructor base se puede inicializar directamente de la siguiente manera:

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base (5) {
   constructor() public {}
}
  • El constructor base se puede inicializar indirectamente de la siguiente manera:

pragma solidity ^0.5.0;

contract Base {
   uint data;
   constructor(uint _data) public {
      data = _data;   
   }
}
contract Derived is Base {
   constructor(uint _info) Base(_info * _info) public {}
}
  • No se permiten formas directas e indirectas de inicializar el constructor del contrato base.

  • Si el contrato derivado no pasa argumento (s) al constructor del contrato base, el contrato derivado se volverá abstracto.

El contrato abstracto es aquel que contiene al menos una función sin ninguna implementación. Dicho contrato se utiliza como contrato base. Generalmente, un contrato abstracto contiene tanto funciones implementadas como abstractas. El contrato derivado implementará la función abstracta y utilizará las funciones existentes cuando sea necesario.

En caso de que un contrato derivado no implemente la función abstracta, este contrato derivado se marcará como abstracto.

Ejemplo

Pruebe el siguiente código para comprender cómo funcionan los contratos abstractos en Solidity.

pragma solidity ^0.5.0;

contract Calculator {
   function getResult() public view returns(uint);
}
contract Test is Calculator {
   function getResult() public view returns(uint) {
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Salida

0: uint256: 3

Las interfaces son similares a los contratos abstractos y se crean utilizando interfacepalabra clave. A continuación se presentan las características clave de una interfaz.

  • La interfaz no puede tener ninguna función con la implementación.

  • Las funciones de una interfaz solo pueden ser de tipo externo.

  • La interfaz no puede tener constructor.

  • La interfaz no puede tener variables de estado.

  • La interfaz puede tener enum, estructuras a las que se puede acceder mediante la notación de puntos del nombre de la interfaz.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona la interfaz en Solidity.

pragma solidity ^0.5.0;

interface Calculator {
   function getResult() external view returns(uint);
}
contract Test is Calculator {
   constructor() public {}
   function getResult() external view returns(uint){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Note - Seleccione Prueba en el menú desplegable antes de hacer clic en el botón de implementación.

Salida

0: uint256: 3

Las bibliotecas son similares a los contratos, pero están destinadas principalmente a la reutilización. Una biblioteca contiene funciones que otros contratos pueden llamar. Solidity tiene ciertas restricciones sobre el uso de una biblioteca. A continuación se presentan las características clave de una biblioteca de solidez.

  • Las funciones de la biblioteca se pueden llamar directamente si no modifican el estado. Eso significa que las funciones puras o de visualización solo se pueden llamar desde fuera de la biblioteca.

  • La biblioteca no se puede destruir ya que se supone que es apátrida.

  • Una biblioteca no puede tener variables de estado.

  • Una biblioteca no puede heredar ningún elemento.

  • Una biblioteca no se puede heredar.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona una biblioteca en Solidity.

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++) if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;
      
      //search if value is present in the array using Library function
      uint index = Search.indexOf(data, value);
      return index;
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Note - Seleccione Prueba en el menú desplegable antes de hacer clic en el botón de implementación.

Salida

0: uint256: 3

Usando para

La directiva using A for B; se puede usar para adjuntar funciones de biblioteca de la biblioteca A a un tipo B dado. Estas funciones usarán el tipo de llamador como su primer parámetro (identificado usando self).

Ejemplo

Pruebe el siguiente código para comprender cómo funciona una biblioteca en Solidity.

pragma solidity ^0.5.0;

library Search {
   function indexOf(uint[] storage self, uint value) public view returns (uint) {
      for (uint i = 0; i < self.length; i++)if (self[i] == value) return i;
      return uint(-1);
   }
}
contract Test {
   using Search for uint[];
   uint[] data;
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function isValuePresent() external view returns(uint){
      uint value = 4;      
      
      //Now data is representing the Library
      uint index = data.indexOf(value);
      return index;
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Note - Seleccione Prueba en el menú desplegable antes de hacer clic en el botón de implementación.

Salida

0: uint256: 3

Solidity ofrece una opción para utilizar el lenguaje ensamblador para escribir ensamblados en línea dentro del código fuente de Solidity. También podemos escribir un código ensamblador independiente que luego se convierte en código de bytes. Standalone Assembly es un lenguaje intermedio para un compilador de Solidity y convierte el código de Solidity en un ensamblado independiente y luego en un código de bytes. Podemos usar el mismo lenguaje que se usa en Inline Assembly para escribir código en un ensamblado independiente.

Ensamblaje en línea

El código ensamblador en línea se puede intercalar dentro del código base de Solidity para tener un control más detallado sobre EVM y se usa especialmente al escribir las funciones de la biblioteca.

Un código ensamblador se escribe debajo assembly { ... } bloquear.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona una biblioteca en Solidity.

pragma solidity ^0.5.0;

library Sum {   
   function sumUsingInlineAssembly(uint[] memory _data) public pure returns (uint o_sum) {
      for (uint i = 0; i < _data.length; ++i) {
         assembly {
            o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
         }
      }
   }
}
contract Test {
   uint[] data;
   
   constructor() public {
      data.push(1);
      data.push(2);
      data.push(3);
      data.push(4);
      data.push(5);
   }
   function sum() external view returns(uint){      
      return Sum.sumUsingInlineAssembly(data);
   }
}

Ejecute el programa anterior siguiendo los pasos proporcionados en el capítulo Primera aplicación de Solidity.

Note - Seleccione Prueba en el menú desplegable antes de hacer clic en el botón de implementación.

Salida

0: uint256: 15

Event es un miembro heredable de un contrato. Se emite un evento, almacena los argumentos pasados ​​en los registros de transacciones. Estos registros se almacenan en blockchain y se puede acceder a ellos mediante la dirección del contrato hasta que el contrato esté presente en blockchain. Un evento generado no es accesible desde dentro de los contratos, ni siquiera el que los ha creado y emitido.

Un evento se puede declarar usando la palabra clave event.

//Declare an Event
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);

//Emit an event
emit Deposit(msg.sender, _id, msg.value);

Ejemplo

Pruebe el siguiente código para comprender cómo funciona un evento en Solidity.

Primero cree un contrato y emita un evento.

pragma solidity ^0.5.0;

contract Test {
   event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
   function deposit(bytes32 _id) public payable {      
      emit Deposit(msg.sender, _id, msg.value);
   }
}

Luego acceda al evento del contrato en código JavaScript.

var abi = /* abi as generated using compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceiptContract = ClientReceipt.at("0x1234...ab67" /* address */);

var event = clientReceiptContract.Deposit(function(error, result) {
   if (!error)console.log(result);
});

Debería imprimir detalles similares a los siguientes:

Salida

{
   "returnValues": {
      "_from": "0x1111...FFFFCCCC",
      "_id": "0x50...sd5adb20",
      "_value": "0x420042"
   },
   "raw": {
      "data": "0x7f...91385",
      "topics": ["0xfd4...b4ead7", "0x7f...1a91385"]
   }
}

Solidity proporciona varias funciones para el manejo de errores. Generalmente, cuando ocurre un error, el estado se revierte a su estado original. Otras verificaciones son para evitar el acceso no autorizado al código. A continuación se muestran algunos de los métodos importantes utilizados en el manejo de errores:

  • assert(bool condition)- En caso de que no se cumpla la condición, esta llamada al método provoca un código de operación no válido y se revierte cualquier cambio realizado en el estado. Este método se debe utilizar para errores internos.

  • require(bool condition)- En caso de que no se cumpla la condición, esta llamada al método vuelve al estado original. - Este método se debe utilizar para errores en entradas o componentes externos.

  • require(bool condition, string memory message)- En caso de que no se cumpla la condición, esta llamada al método vuelve al estado original. - Este método se debe utilizar para errores en entradas o componentes externos. Proporciona una opción para proporcionar un mensaje personalizado.

  • revert() - Este método aborta la ejecución y revierte cualquier cambio realizado en el estado.

  • revert(string memory reason)- Este método aborta la ejecución y revierte cualquier cambio realizado en el estado. Proporciona una opción para proporcionar un mensaje personalizado.

Ejemplo

Pruebe el siguiente código para comprender cómo funciona el manejo de errores en Solidity.

pragma solidity ^0.5.0;

contract Vendor {
   address public seller;
   modifier onlySeller() {
      require(
         msg.sender == seller,
         "Only seller can call this."
      );
      _;
   }
   function sell(uint amount) public payable onlySeller { 
      if (amount > msg.value / 2 ether)
         revert("Not enough Ether provided.");
      // Perform the sell operation.
   }
}

Cuando se llama a revertir, devolverá los datos hexadecimales como se indica a continuación.

Salida

0x08c379a0                     // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data