DEV Community

Cover image for Solidity en 15 minutos
Ahmed Castro
Ahmed Castro

Posted on • Edited on

Solidity en 15 minutos

Introducción a Solidity

  • Solidity es el lenguaje más popular para Contratos Inteligentes
  • Solidity está inspirado en Javascript, C++ y Python
  • Solidity fue propuesto en 2014 por Gavin Wood, fue desarrollado por un equipo liderado para Christian Reitwiessner. Su primera versión estable fue lanzada en 2018
  • Solidity tiene muy buen soporte por parte de su comunidad

Compiladores de Solidity

Mi recomendación será que inicies con el Remix IDE donde puedes compilar, lanzar e interactuar con contratos desde el browser sin instalar nada adicional.

Si deseas compilar un programa en solidity desde la terminal puedes instalar solc desde npm.

npm install -g solc
solcjs --version
solcjs --bin  MyContract.sol
Enter fullscreen mode Exit fullscreen mode

Cuando estés listo para un ambiente de programación que te ayude a largo plazo te recomiendo Hardhat, Truffle o Foundry. Los tres son muy buenas opciones.

Obtener fondos de prueba en metamask

Una vez descargada la billetera de metamask, mi recomendación es activar los testnets para poder acceder a Sepolia Testnet, una vez hecho eso puedes obtener fondos de manera gratuita desde el Sepolia Faucet.

"Hola Mundo!" en Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract HelloWorld {
    string hello = "Hola Mundo!";

    function setHello(string memory hello_) public
    {
        hello = hello_;
    }

    function getHello() public view returns (string memory)
    {
        return hello;
    }
}
Enter fullscreen mode Exit fullscreen mode

Licencia

La primera línea del contrato ejemplo es un comentario donde se coloca la licencia del Código. Este comentario es opcional pero te recomiendo agregar la licencia MIT que permite a los demás usar tu código con cualquier fin siempre y cuando te mencionen.

// SPDX-License-Identifier: MIT
Enter fullscreen mode Exit fullscreen mode

Versión

La segunda línea es la versión de solidity, en este caso estamos usando la 0.8.19 que es la más nueva a la fecha que se escribió este artículo.

pragma solidity 0.8.19;
Enter fullscreen mode Exit fullscreen mode

Declarar Variables

Solidity es fuertemente tipeado, es decir que al declarar una variable debes colocar su tipo.

uint number = 150;
uint pi = 3.141 ether;
string memory name = "Filosofia Codigo is great";
string[] studentNames = ["Pedro", "Ana", "Juan", "Luisa"];
bool isSolidityAwesome = true;
Enter fullscreen mode Exit fullscreen mode

Tipos de datos en Solidity

A continuación mostramos los tipos de datos en Solidity

  • uint

Uint se refiere a "unsigned integer" o entero sin signo. Por defecto se declara como un entero de 256 bits pero se puede definir el tamaño para ahorrar gas. El tamaño puede ser cualquier múltiplo de 8 desde uint8 hasta uint256.

uint num1 = 456;
uint16 num2 = 3;
uint num2 = 1235.56 ether;
Enter fullscreen mode Exit fullscreen mode

Solidity no soporta puntos flotantes pero tiene las palabras reservadas ether y wgei son unidades de medida que representan 10¹⁸ y 10⁹ respectivamente. wei es la unidad mínima y la que utilizamos por defecto. Es decir que 1000000000000000000 wei = 1 ether.

  • int

Para represntar número con signo podemos usar int.

int num1 = -234;
int num2 = 432;
int num3 = -5.7 ether;
Enter fullscreen mode Exit fullscreen mode
  • Strings

Colecciones de caracteres. Son bastante usadas para guardar información que sea fácil de leer para usuarios.

string name = "Welcome to Solidity";
Enter fullscreen mode Exit fullscreen mode
  • Address

Representa una dirección de una billetera o contrato en el blockchain.

address owner = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;
Enter fullscreen mode Exit fullscreen mode
  • Mapas o mapping

Representan pares de LLaves y Valores. Son una manera muy eficiente de guardar leer colecciones de datos.

mapping(string account => uint balance) balances;
balances["Ana"] = 50;
balances["Juan"] = 100;
Enter fullscreen mode Exit fullscreen mode
  • Arreglos

Los usamos para guardar colecciones de datos. Úsalos únicamente para datos rígidos, que no cambien mucho. Si deseas una colección más dinámica usa los Mapas para ahorrar gas.

uint[] myArray = [100, 50, 300];
Enter fullscreen mode Exit fullscreen mode
  • Booleanos

Usados para representar datos del estilo verdadero o falso.

bool valid = true
bool invalid = false
Enter fullscreen mode Exit fullscreen mode

Operar Strings en Solidity

Comparar Strings

A diferencia de otros lenguajes, en Solidity no podemos comparar dos strings usando el operador ==. Para hacerlo debemos primero convertirlas a sus hashes respectivos y luego comparar los resultados.

string memory a = "Hola!";
string memory b = "Hola!";
bool stringsAreEqual = keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
Enter fullscreen mode Exit fullscreen mode

Concatenar de Strings

string memory a = "Hola ";
string memory b = "Mundo!";
string memory holaMundo = string.concat(a,b);
Enter fullscreen mode Exit fullscreen mode

¿Cuándo usar memory en Solidity?

Cuando una String se declara en memoria durante el tiempo de ejecución colocamos la palabra memory. Esto es cuando declaramos una variable como parámetro o dentro de una función. Únicamente las Strings declaradas como variables de estado de un contrato no llevan esta palabra reservada, esto significa que son guardadas on-chain y no en memoria.

contract ExampleStrings {
    string stateVariable;
    function exampleFunction(string memory parameterVariable) public {
      string memory scopeVariable;
    }
}
Enter fullscreen mode Exit fullscreen mode

Enum en solidity

Los enums nos permiten crear nuestros propios tipos de variables para hacer el código más legible. Funcionan muy similar a C donde los elementos del Enum pueden ser representados por enteros donde el primer elemento es 0;

enum Color { Red, Green, Blue }
Color color = Color.Blue;
Enter fullscreen mode Exit fullscreen mode

Struct en solidity

Las Structs nos permiten crear nuevos tipos de variables que agrupan diferentes valores.

struct Voter {
    uint weight;
    bool voted;
    uint vote;
}
Voter voter = Voter(10, true, 4);
Enter fullscreen mode Exit fullscreen mode

Mapas en Solidity

Declarar un Mapa

Los mapas pueden ser una combinación de diferentes tipos de pares. El valor asociado con una llave puede ser otro mapa.

mapping(address account => uint balance) balances;
mapping(uint256 nftId => address owner) nftOwner;
mapping(uint256 => mapping(string => bool)) nestedExample;
Enter fullscreen mode Exit fullscreen mode

Asignar un valor

balances[0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB] = 10 ether;
Enter fullscreen mode Exit fullscreen mode

Leer un valor

address owner = nftOwner[10];
Enter fullscreen mode Exit fullscreen mode

Cómo borrar un elemento de un Mapa

Técnicamente no existe una función para borrar un elemento. Pero podemos regresarlo a su valor inicial que es 0 indicando así que el elemento no existe.

balances[10] = 0;
names[10] = "";
addresses[10] = address(0);
Enter fullscreen mode Exit fullscreen mode

Funciones

En Solidity existen diferentes tipos de funciones.

  • Funciones de escritura

Toda que escribe o cambia un valor en el blockchain, es decir que cambia su estado, debe pagar gas.

uint balance;

function setBalance(uint balance_) public {
    balance = balance_;
}
Enter fullscreen mode Exit fullscreen mode
  • Funciones payable en Solidity

Colocamos la palabra reservada payable en una función que sea capaz de recibir ETH. La cantidad de ETH recibida quedará almacenada en la variable reservada msg.value.

uint public totalPayed;

function payToContract() public payable
{
    totalPayed += msg.value;
}
Enter fullscreen mode Exit fullscreen mode
  • Funciones view en Solidity

Colocamos la palabra reservada view en una función cuando esta no escribe nada en el blockchain, cuando no cambia el estado.

uint balance;

function getBalance() public view returns(uint) {
    return balance;
}
Enter fullscreen mode Exit fullscreen mode
  • Funciones pure en Solidity

Colocamos la palabra reservada pure en una función cuando esta no escribe ni lee el estado del blockchain.

function getPi() public pure returns(uint) {
    return 3.14 ether;
}
Enter fullscreen mode Exit fullscreen mode

Visibilidad de funciones en solidity

  • Funciones public en Solidity

Para que nuestra función puede ser llamada desde afuera del contrato debemos agregar la palabra reservada public.

string name;
function getName() public view returns(string memory) {
    return name;
}
Enter fullscreen mode Exit fullscreen mode
  • Funciones internal y private en Solidity

Las funciones internal se pueden acceder únicamente desde adentro del Smart Contract. Es el mismo caso con las funciones private pero estas no serán heredadas en contratos hijos.

function sum(uint a, uint b) internal pure returns(uint) {
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

If/Else en Solidity

function compare(uint a, uint b) public pure returns(int) {
    if(a > b)
    {
        return 1;
    } else if(a == b)
    {
        return 0;
    } else
    {
        return -1;
    }
}
Enter fullscreen mode Exit fullscreen mode

Cíclo While en Solidity

uint num = 0;
while(num < 10)
{
  mintNFT();
  num += 1;
}
Enter fullscreen mode Exit fullscreen mode

Cíclo For en Solidity

for(uint i=0; i<10; i++)
{
  mintNFT();
}
Enter fullscreen mode Exit fullscreen mode

Palabras reservadas en Solidity

  • msg.send

Dirección de quien envió la transacción.

address sender = msg.sender;
Enter fullscreen mode Exit fullscreen mode
  • msg.value

Cantidad de wei enviada al contrato.

uint weiSent = msg.value;
Enter fullscreen mode Exit fullscreen mode
  • block.timestamp

Epoch en segundos del bloque que ha sido minado mas recientemente. Usa el formato Unix.

uint currentEpoch = block.timestamp;
Enter fullscreen mode Exit fullscreen mode
  • block.number

Número de bloque que fue minado mas recientemente.

uint blockNumber = block.number;
Enter fullscreen mode Exit fullscreen mode

Estas son las palabras reservadas más comunes pero en este enlace puedes ver la lista completa.

¿Cómo agregar comentarios en Solidity?

Puedes agregar comentarios de dos maneras.

Comentarios de una línea

// Este es un comentario de una línea.
Enter fullscreen mode Exit fullscreen mode

Comentarios multilínea

/*
Este
es
multilínea
*/
Enter fullscreen mode Exit fullscreen mode

Event y Emit en Solidity

Los eventos pueden emitirse en cualquier función de tipo escritura y pueden detallar los cambios que ocurrieron en las transacciones. Usamos los eventos para que los usuarios finales puedan ver el historial de un contrato inteligente en plataformas como Etherscan o Blocscout. Y recientemente se utilizan estos eventos para organizar información y poder hacer búsquedas con ayuda de proyectos como TheGraph.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract HelloWorld {
    string hello = "Hola Mundo!";

    event HelloValueChanged(address sender, string newValue);

    function setHello(string memory hello_) public
    {
        hello = hello_;
        emit HelloValueChanged(msg.sender, hello);
    }
}
Enter fullscreen mode Exit fullscreen mode

¿Qué es modifier en Solidity?

Los modifiers nos permiten agregar código en funciones para hacer el código más legible y menos repetitivo.

modifier checkBalance(uint decreaseAmount) {
    require(balance >= decreaseAmount, "Not enough balance.");
    _;
}

function decreaseBalance(uint balance_) internal checkBalance(balance_) {
    balance -= balance;
}
Enter fullscreen mode Exit fullscreen mode

Manejo de errores en Solidity

¿Qué es revert en Solidity?

Revert detiene la ejecución de la transacción y revierte al estado anterior, es decir que deshace los cambios que se hicieron. La usamos cuando se encuentra un error o cuando las condiciones para ejecutar una función no se cumplen (por ejemplo, si alguien desea retirar antes que se cumpla un deadline en un contrato estilo timelock. En general el revert() es bastante útil pero usualmente preferimos usar require que veremos a continuación.

if(balance == 0)
  revert();
Enter fullscreen mode Exit fullscreen mode

¿Qué es require en Solidity?

El require revierte la transacción si no se cumple la condición en su primer parámetro. Es similar al revert() pero adicionalmente devuelve un mensaje de error que luego puede ser leído por el usuario, ya sea on-chain o en herramientas como etherscan o blockscout.

require(balance > 0, "Not enough balance");
Enter fullscreen mode Exit fullscreen mode

¿Qué es assert en Solidity?

El assert devuelve un error cuando no se cumple la condición enviada por parámetro. A diferencia del revert() y require, el assert no devuelve un mensaje fácil de leer para los usuarios así que es una función pensada para hacer pruebas internas. En teoría, un error de tipo assert jamás debería ser encontrado por un usuario final. Únicamente debería ser encontrado por herramientas de pruebas como unit tests, fuzzing u otras herramientas de pruebas estáticas. Si estás en duda, no utilices assert, mejor utiliza require para hacer un contrato seguro y fácil de operar. Personalmente, pienso que assert es un lujo ya que incurre en costos de gas a cambio de útilidad mínima.

assert(balance >= totalSupply);
Enter fullscreen mode Exit fullscreen mode

Interfaces en Solidity

Al implementar una interfaz en un contrato con la palabra reservada is obligamos al contrato a implementar todos los métodos de la interfaz. Como programadores, esto nos ayuda a que no olvidemos declarar todos los métodos. Y también ayuda a hacer el código más fácil de leer para el público en general.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IPersonalProfile
{
    function getName() external returns(string memory);
    function getWallet() external returns(address);
}

contract PersonalProfile is IPersonalProfile{
    string name;
    address wallet;

    constructor(string memory name_, address wallet_)
    {
        name = name_;
        wallet = wallet_;
    }

    function getName() external view returns(string memory)
    {
        return name;
    }

    function getWallet() external view returns(address)
    {
        return wallet;
    }
}
Enter fullscreen mode Exit fullscreen mode

Interoperabilidad de contratos

La interoperabilidad entre contratos es cuando un contrato ejecuta funciones en otro contrato. La interoperabilidad es parte de la naturaleza del blockchain pues es un sistema abierto y descentralizado. Las interfaces nos ayudan a hacer esto posible.

Supongamos que tenemos lanzado en el blockchain el siguiente contrato.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract ContractA
{
    string variable;
    function writeFunction(string memory param) public
    {
        variable = param;
    }

    function readFunction() public view returns(string memory)
    {
        return variable;
    }
}
Enter fullscreen mode Exit fullscreen mode

Podemos operar este contrato desde otro contrato si tenemos la interfaz y el address donde fue lanzado. En este caso lo enviamos desde el constructor pero puede ser el parámetro de una función o un valor estático.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IContractA
{
    function writeFunction(string memory param) external;
    function readFunction() external view returns(string memory);
}

contract ContractB
{
    IContractA contractA;
    constructor(address contractAAddress)
    {
        contractA = IContractA(contractAAddress);
    }

    function writeInteroperability(string memory param) public
    {
        contractA.writeFunction(param);
    }

    function readInteroperability() public view returns(string memory)
    {
        return contractA.readFunction();
    }
}
Enter fullscreen mode Exit fullscreen mode

Herencia en Solidity

Los contratos en Solidity pueden implementar herencia de una manera muy similar a la de cualquier otro lenguaje orientado a objetos. Para indicar que se está heredando usamos la palabra reservada is. También podemos hacer uso de las palabras reservadas virtual y override para sobreescribir métodos.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract Animal
{
    string name;
    string color;

    constructor(string memory name_, string memory color_)
    {
        name = name_;
        color = color_;
    }

    function speak() public pure virtual returns(string memory)
    {
        return "Ggggg";
    }
}

contract Cat is Animal
{
    constructor(string memory name_, string memory color_) Animal(name_, color_)
    {
    }

    function speak() public pure override returns(string memory)
    {
        return "Miau";
    }

    function walk() public pure returns(string memory)
    {
        return "Puedo caminar en cuatro patas.";
    }
}
Enter fullscreen mode Exit fullscreen mode

Librerías en Solidity

Las librerías nos ayudan a mantener nuestro código ordenado y menos repetitivo. Las librerías no pueden almacenar variables de estado, no se pueden heredar y se limitan a ofrecer funciones de tipo lectura.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

library ArithmeticLibrary
{
    function add(uint a, uint b) public pure returns(uint) {
        return a + b;
    }

    function sub(uint a, uint b) public pure returns(uint) {
        return a - b;
    }
}

contract Calculator {
    using ArithmeticLibrary for uint;

    function getSum(uint firstNumber, uint secondNumber) public pure returns(uint) {
        return firstNumber.add(secondNumber);
    }

    function getSub(uint firstNumber, uint secondNumber) public pure returns(uint) {
        return firstNumber.sub(secondNumber);
    }
}
Enter fullscreen mode Exit fullscreen mode

Importar librerías y contratos en Solidity

Podemos importar librerías y contratos con la palabra reservada import. Le podemos proveer una dirección relativa a otro archivo .sol. En caso que estemos usando Remix, podemos proveerle un link directo o un archivo almacenado en una librería de npm. Las importar archivos nos ayuda a mantener nuestro código más ordenado y más seguro siempre y cuando usemos código de fuentes confiables, auditadas y probadas a través del tiempo.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract SimpleToken is ERC20 {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply * 1 ether);
    }
}
Enter fullscreen mode Exit fullscreen mode

¡Gracias por ver este tutorial!

Sígueme en dev.to y en Youtube para todo lo relacionado al desarrollo en Blockchain en Español.

Top comments (0)