Inicio / Blockchain / Blockchain: De Cero a Desarrollador / Solidity: Tipos Avanzados y Patrones

Solidity: Tipos Avanzados y Patrones

Storage/memory/calldata, herencia, interfaces y libraries.

Intermedio Funciones
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Solidity: Tipos Avanzados y Patrones

En esta lección profundizaremos en las estructuras de datos avanzadas de Solidity, la gestión de memoria y los patrones de diseño fundamentales para escribir smart contracts robustos.

Data Locations: Storage, Memory, Calldata

En Solidity, las variables de tipo referencia deben especificar dónde se almacenan:

contract DataLocations {
    // storage: persistente en la blockchain (caro)
    uint[] public storageArray;

    function ejemplos(uint[] calldata _input) external {
        // calldata: solo lectura, datos del input de la función
        // Más barato que memory porque no se copia
        uint primerElemento = _input[0];

        // memory: temporal, solo durante la ejecución
        uint[] memory tempArray = new uint[](3);
        tempArray[0] = 1;
        tempArray[1] = 2;
        tempArray[2] = 3;

        // storage: referencia al estado persistente
        storageArray.push(primerElemento);
    }
}

Comparación de costos

Location    Persistencia    Costo           Uso típico
──────────────────────────────────────────────────────
storage     Permanente      20,000 gas      Estado del contrato
memory      Temporal        3 gas           Variables locales
calldata    Temporal        Mínimo          Parámetros external

Mappings Avanzados

Mapping anidado

contract MappingsAvanzados {
    // Mapping simple
    mapping(address => uint) public balances;

    // Mapping anidado: allowances para ERC-20
    mapping(address => mapping(address => uint)) public allowances;

    // Mapping a struct
    struct Producto {
        string nombre;
        uint precio;
        bool existe;
    }
    mapping(uint => Producto) public productos;

    // Mapping con array
    mapping(address => uint[]) public historialCompras;

    function agregarProducto(uint _id, string memory _nombre, uint _precio) public {
        productos[_id] = Producto(_nombre, _precio, true);
    }
}

Iterable Mapping Pattern

Los mappings no son iterables por defecto. Este patrón lo soluciona:

contract IterableMapping {
    mapping(address => uint) public balances;
    address[] public keys;
    mapping(address => bool) public inserted;

    function set(address _key, uint _val) public {
        balances[_key] = _val;
        if (!inserted[_key]) {
            inserted[_key] = true;
            keys.push(_key);
        }
    }

    function getAll() public view returns (address[] memory, uint[] memory) {
        uint[] memory vals = new uint[](keys.length);
        for (uint i = 0; i < keys.length; i++) {
            vals[i] = balances[keys[i]];
        }
        return (keys, vals);
    }
}

Herencia

Solidity soporta herencia múltiple con linearización C3:

contract Animal {
    string public especie;

    constructor(string memory _especie) {
        especie = _especie;
    }

    function sonido() public pure virtual returns (string memory) {
        return "...";
    }
}

contract Mascota is Animal {
    string public nombre;

    constructor(string memory _nombre, string memory _especie)
        Animal(_especie)
    {
        nombre = _nombre;
    }
}

contract Perro is Mascota {
    constructor(string memory _nombre)
        Mascota(_nombre, "Canino")
    {}

    function sonido() public pure override returns (string memory) {
        return "Guau!";
    }
}

Interfaces y Abstract Contracts

// Interface: define el "qué" sin implementación
interface IERC20 {
    function totalSupply() external view returns (uint);
    function balanceOf(address account) external view returns (uint);
    function transfer(address to, uint amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint value);
}

// Abstract: puede tener funciones implementadas y sin implementar
abstract contract Pausable {
    bool public paused;

    modifier whenNotPaused() {
        require(!paused, "Contrato pausado");
        _;
    }

    function _pause() internal {
        paused = true;
    }

    // Función abstracta (sin implementación)
    function emergencyAction() public virtual;
}

Bibliotecas (Libraries)

library SafeMath {
    function add(uint a, uint b) internal pure returns (uint) {
        uint c = a + b;
        require(c >= a, "Overflow");
        return c;
    }
}

library ArrayUtils {
    function contains(uint[] memory arr, uint val) internal pure returns (bool) {
        for (uint i = 0; i < arr.length; i++) {
            if (arr[i] == val) return true;
        }
        return false;
    }
}

contract UsaLibreria {
    using SafeMath for uint;
    using ArrayUtils for uint[];

    function ejemplo() public pure returns (uint) {
        uint a = 100;
        return a.add(50); // SafeMath.add(100, 50)
    }
}

Receive y Fallback

Funciones especiales para recibir ETH:

contract Receptor {
    event RecibidoETH(address sender, uint amount);

    // Se ejecuta cuando se envía ETH sin data
    receive() external payable {
        emit RecibidoETH(msg.sender, msg.value);
    }

    // Se ejecuta cuando se llama una función que no existe
    // o cuando se envía ETH con data y no hay receive
    fallback() external payable {
        emit RecibidoETH(msg.sender, msg.value);
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

Flujo de decisión

                    ETH enviado al contrato
                           │
                    ¿msg.data vacío?
                    /              \
                  Sí                No
                  │                  │
          ¿Existe receive()?   ¿Existe fallback()?
          /           \         /           \
        Sí            No      Sí            No
        │              │       │             │
    receive()     fallback()  fallback()   REVERT

Envío de ETH

Tres formas de enviar ETH, con diferentes implicaciones:

contract EnviarETH {
    // transfer: 2300 gas, revierte en error (RECOMENDADO para simple)
    function viaTransfer(address payable _to) public payable {
        _to.transfer(msg.value);
    }

    // send: 2300 gas, retorna bool (NO RECOMENDADO)
    function viaSend(address payable _to) public payable {
        bool sent = _to.send(msg.value);
        require(sent, "Fallo al enviar");
    }

    // call: gas personalizable, retorna (bool, data)
    // RECOMENDADO para contratos complejos
    function viaCall(address payable _to) public payable {
        (bool sent, ) = _to.call{value: msg.value}("");
        require(sent, "Fallo al enviar");
    }
}

ABI Encoding

contract ABIEncoding {
    // abi.encode: encoded con padding (estándar)
    function encode(uint x, string memory s) public pure returns (bytes memory) {
        return abi.encode(x, s);
    }

    // abi.encodePacked: sin padding (más compacto, riesgo de colisión)
    function encodePacked(uint x, string memory s) public pure returns (bytes memory) {
        return abi.encodePacked(x, s);
    }

    // abi.encodeWithSignature: para llamadas a contratos
    function encodeCall() public pure returns (bytes memory) {
        return abi.encodeWithSignature("transfer(address,uint256)",
            0x742d35Cc6634C0532925a3b844Bc9e7595f2bD09,
            100
        );
    }
}

Resumen

Solidity maneja datos en tres ubicaciones: storage (persistente y costoso), memory (temporal), y calldata (solo lectura para inputs). Los mappings y structs permiten estructuras complejas, la herencia y las interfaces organizan el código, y las libraries reutilizan lógica. Comprender receive/fallback y las formas de enviar ETH es crucial para contratos que manejan fondos.

🔒

Ejercicio práctico disponible

Registro de usuarios con structs y mappings

Desbloquear ejercicios
// Registro de usuarios con structs y mappings
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes