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
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;
}
}
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
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;
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;
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;
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;
- Strings
Colecciones de caracteres. Son bastante usadas para guardar información que sea fácil de leer para usuarios.
string name = "Welcome to Solidity";
- Address
Representa una dirección de una billetera o contrato en el blockchain.
address owner = 0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB;
- 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;
- 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];
- Booleanos
Usados para representar datos del estilo verdadero o falso.
bool valid = true
bool invalid = false
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));
Concatenar de Strings
string memory a = "Hola ";
string memory b = "Mundo!";
string memory holaMundo = string.concat(a,b);
¿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;
}
}
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;
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);
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;
Asignar un valor
balances[0xb6F5414bAb8d5ad8F33E37591C02f7284E974FcB] = 10 ether;
Leer un valor
address owner = nftOwner[10];
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);
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_;
}
- 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;
}
- 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;
}
- 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;
}
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;
}
- 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;
}
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;
}
}
Cíclo While en Solidity
uint num = 0;
while(num < 10)
{
mintNFT();
num += 1;
}
Cíclo For en Solidity
for(uint i=0; i<10; i++)
{
mintNFT();
}
Palabras reservadas en Solidity
- msg.send
Dirección de quien envió la transacción.
address sender = msg.sender;
- msg.value
Cantidad de wei enviada al contrato.
uint weiSent = msg.value;
- block.timestamp
Epoch en segundos del bloque que ha sido minado mas recientemente. Usa el formato Unix.
uint currentEpoch = block.timestamp;
- block.number
Número de bloque que fue minado mas recientemente.
uint blockNumber = block.number;
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.
Comentarios multilínea
/*
Este
es
multilínea
*/
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);
}
}
¿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;
}
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();
¿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");
¿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);
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;
}
}
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;
}
}
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();
}
}
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.";
}
}
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);
}
}
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);
}
}
¡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)