Ethereum: de historia a labs
Artículo 17 de 48
35%
completado al llegar aquí
Sistemas distribuidosNota de investigación11 min de lectura
ERC-721 y ERC-1155: propiedad, metadata y safe transfers
ERC-721 y ERC-1155 desde el estado: dueño por id, operadores, el hook onERC721Received, lotes en ERC-1155 y metadata que vive fuera del contrato.
Ruta Ethereum: 0/48
Prerequisitos recomendados
Si alguno de estos conceptos todavía está medio nebuloso, empieza por aquí. Te ahorra tropiezos y un par de cejas fruncidas.
Un juego quiere representar dos cosas: espadas únicas, cada una con su historia, y monedas de oro intercambiables. Con ERC-20 las monedas son fáciles, pero las espadas no, porque ERC-20 solo sabe contar cantidades. Las espadas necesitan una pregunta distinta: no "¿cuánto tiene Ana?", sino "¿quién es el dueño de la espada 7?". De esa pregunta nacen ERC-721 y, después, ERC-1155.
Un NFT (non-fungible token) es un token que no es intercambiable por otro del mismo contrato, porque cada uno tiene un identificador propio. ERC-721 define la interfaz estándar para gestionar, poseer y comerciar esos tokens, cada uno identificado por un uint256 llamado tokenId [1]. Este capítulo lo lee igual que leímos ERC-20: qué estado guarda, quién puede cambiarlo y qué evidencia deja.
ERC-721 o ERC-1155 para un caso concreto
Brújula de lectura
Actor, evidencia y estado
Actor
Dueño, aprobado u operadorTres formas distintas de estar autorizado a mover un token.
Evidencia
Transfer, TransferSingle, TransferBatchEventos suficientes para reconstruir propiedad y saldos sin leer storage.
Estado
Dueño por id o saldo por (cuenta, id)La imagen y los atributos no forman parte de este estado.
Preguntas guía
- ¿Qué diferencia hay entre aprobar una dirección para un token y aprobar un operador para todos?
- ¿Por qué existen dos funciones de transferencia en ERC-721?
- ¿Qué ejecuta exactamente un contrato receptor cuando le llega un NFT?
- Si el servidor de metadata desaparece, ¿qué sigue siendo verdad en la cadena?
Del saldo al dueño: el estado de ERC-721
En ERC-20 la función central es balanceOf. En ERC-721 es ownerOf(tokenId), que devuelve la dirección dueña y revierte si el token no existe. balanceOf(owner) sigue existiendo, pero cuenta cuántos NFTs tiene una dirección. Por dentro, una implementación típica guarda un mapping de id a dueño y otro de dueño a cantidad.
Los permisos tienen dos niveles. approve(to, tokenId) autoriza a una dirección sobre un token concreto, y esa aprobación se borra en cada transferencia. setApprovalForAll(operator, true) autoriza a un operador sobre todos los tokens presentes y futuros del dueño en ese contrato [1]. El segundo es el que piden los mercados, y es el más delicado: equivale al permiso infinito de ERC-20, pero sobre toda una colección.
Todo contrato ERC-721 debe implementar también ERC-165, un mecanismo para preguntar a un contrato si implementa una interfaz. supportsInterface(0x80ac58cd) devuelve true en un ERC-721 [2]. Un mercado lo usa antes de listar un contrato, y un receptor lo usa para saber qué hooks esperar.
Traza de ejecución: un safeTransferFrom hacia un contrato
ERC-721 tiene dos formas de transferir. transferFrom mueve el token y no pregunta nada al receptor; el propio estándar advierte en mayúsculas que quien llama es responsable de confirmar que el destino puede recibir NFTs, o el token puede perderse para siempre. safeTransferFrom añade una comprobación al final: si el destino tiene código, lo llama.
Sigamos la llamada paso a paso. El token comprueba que quien llama es el dueño, la dirección aprobada para ese id o un operador. Actualiza el dueño, borra la aprobación y emite Transfer. Después mira si to tiene código. Si no lo tiene, termina. Si lo tiene, llama onERC721Received(operator, from, tokenId, data) en el receptor y exige que devuelva 0x150b7a02, el selector de esa función. Cualquier otro valor, o un revert del receptor, revierte toda la transacción.
safeTransferFrom en ERC-721: el hook del receptor decide si la transferencia queda
Cómo leer esta figura
Diagrama de flujo. safeTransferFrom(from, to, id) entra al paso 1, que pregunta si quien llama es dueño, aprobado u operador; si no, una flecha roja baja a una caja de revert de toda la transacción. Si sí, el paso 2 asigna to como dueño, borra la aprobación y emite Transfer. El paso 3 pregunta si to tiene código: si no, termina con la transferencia aplicada; si sí, el paso 4 hace una llamada externa a onERC721Received, donde corre código del receptor. Si devuelve 0x150b7a02 la transferencia queda aplicada; si devuelve otro valor o revierte, la flecha roja lleva al revert, y el token vuelve a from.
Este receptor ilustrativo, que compila con OpenZeppelin Contracts 5.x, acepta NFTs de una sola colección y anota quién depositó cada uno:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
contract Boveda721 is IERC721Receiver {
address public immutable collection;
mapping(uint256 => address) public depositor;
error WrongCollection(address sender);
constructor(address collection_) {
collection = collection_;
}
function onERC721Received(
address, /* operator */
address from,
uint256 tokenId,
bytes calldata /* data */
) external returns (bytes4) {
if (msg.sender != collection) revert WrongCollection(msg.sender);
depositor[tokenId] = from;
return IERC721Receiver.onERC721Received.selector; // 0x150b7a02
}
}La comprobación de msg.sender importa porque cualquiera puede llamar onERC721Received directamente con datos inventados. Dentro del hook, msg.sender siempre es el contrato del token, como indica el estándar; el resto de parámetros son lo que ese contrato decidió enviar. Un receptor que confía en from o tokenId sin validar el emisor acepta depósitos que nunca ocurrieron.
ERC-1155: un contrato, muchos ids, transferencias en lote
ERC-1155 parte de una queja práctica: con ERC-20 y ERC-721 cada tipo de token o colección exige su propio contrato. Un juego con miles de objetos desplegaría miles de contratos y pediría miles de aprobaciones. ERC-1155 define un solo contrato donde cada id es un tipo de token configurable, fungible o no [3]. Volviendo al juego: el oro puede ser el id 0 con millones de unidades y cada espada un id con una sola unidad.
El estado pasa a ser balanceOf(owner, id). safeBatchTransferFrom mueve varios ids en una sola llamada y emite TransferBatch. No hay aprobación por token ni por cantidad: solo setApprovalForAll para operadores. Y no existe transferFrom sin comprobación: el estándar solo admite transferencias seguras, de modo que un contrato receptor siempre recibe onERC1155Received (selector 0xf23a6e61) u onERC1155BatchReceived.
La metadata también cambia de forma. ERC-1155 permite una URI con la cadena {id}, que los clientes deben sustituir por el id en hexadecimal, en minúsculas, sin 0x y rellenado a 64 caracteres. Una sola cadena en la cadena describe así miles de tokens. OpenZeppelin añade una observación útil: a diferencia de ERC-20, ERC-1155 no tiene decimals [4].
Riesgo operativo: metadata mutable y hooks que llaman de vuelta
tokenURI(tokenId) devuelve una URI que puede apuntar a un JSON con nombre, descripción e imagen. El contrato registra quién es dueño del id 7; casi nunca registra cómo es la espada 7. La guía de OpenZeppelin lo dice sin rodeos para ERC-1155: la información del objeto no está en la cadena, así que quien controla el JSON puede cambiar sus atributos y, en un juego, cambiar las reglas [4]. Al evaluar un NFT, pregunta quién puede cambiar la URI y dónde vive el archivo.
Los hooks son llamadas externas, y toda llamada externa cede el control. La documentación de OpenZeppelin advierte que el hook de ERC-721 añade una llamada externa que potencialmente crea una vulnerabilidad de reentrancy [5]. Un contrato que acuña con _safeMint y actualiza su contador después de la llamada puede ser reingresado por el receptor a mitad de la operación. La defensa es la de siempre: escribir el estado antes del hook o usar un guardia de reentrancy.
Por último, setApprovalForAll es el permiso más amplio de ambos estándares y no caduca. Una firma o transacción aprobando a un operador malicioso le entrega la colección entera, incluidos tokens que el usuario reciba después. Revisar y revocar operadores debería ser parte de la higiene de cualquier cartera, igual que los allowances de ERC-20.
Conexiones dentro de la serie
- Antes: ERC-20: simple por fuera, tramposo por dentro presenta approve, allowance y los permisos que no caducan.
- Base técnica: Solidity sin magia: contratos como máquinas de estado explica por qué ceder el control con una llamada externa es un riesgo.
- Después: ERC-4626: vaults, shares y contabilidad peligrosa vuelve a tokens fungibles, ahora como participaciones de un fondo.
- Más adelante: Reentrancy: la llamada externa que muerde de vuelta analiza ataques a través de hooks como estos.
Bibliografía académica
- [1]
W. Entriken, D. Shirley, J. Evans and N. Sachs, "ERC-721: Non-Fungible Token Standard," Ethereum Improvement Proposals, no. 721, Jan. 2018.
Interfaz ERC721 (ownerOf, safeTransferFrom, approve, setApprovalForAll), ERC721TokenReceiver, extensión de metadata y tokenURI.
- [2]
C. Reitwießner, N. Johnson, F. Vogelsteller, J. Baylina, K. Feldmeier and W. Entriken, "ERC-165: Standard Interface Detection," Ethereum Improvement Proposals, no. 165, Jan. 2018.
supportsInterface(bytes4) y cálculo de identificadores de interfaz.
- [3]
W. Radomski, A. Cooke, P. Castonguay, J. Therien, E. Binet and R. Sandford, "ERC-1155: Multi Token Standard," Ethereum Improvement Proposals, no. 1155, Jun. 2018.
balanceOf(owner, id), safeBatchTransferFrom, TransferSingle/TransferBatch, solo transferencias seguras, setApprovalForAll y sustitución {id} en URIs.
- [4]
OpenZeppelin, "ERC-1155," OpenZeppelin Contracts 5.x Documentation.
Lotes, ausencia de decimals, sustitución de {id}, metadata fuera de la cadena modificable y ERC1155InvalidReceiver.
- [5]
OpenZeppelin, "ERC 721 API Reference," OpenZeppelin Contracts 5.x Documentation.
safeTransferFrom y _safeMint llaman a onERC721Received; advertencia de llamada externa con riesgo de reentrancy.
Rutas vecinas
Si este tema te abrió hambre, sigue por aquí.
Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.
Series relacionadas
HotStuff: de cero a estado del arte
Ruta progresiva para entender consenso BFT moderno: quórums, sincronía parcial, locks, taxonomía one/two/three-chain, QC/highQC, multipipeline, view-change, responsiveness, AggQC, Fast-HotStuff, sync y frontera DAG-BFT.
Substrate: del runtime al consenso propio
Serie de 20 capítulos, de neófito a experto, sobre Substrate (Polkadot SDK): runtime en Wasm, FRAME, extrinsics, estado, pesos, actualizaciones sin fork, Aura, BABE, GRANDPA, sincronización, parachains, XCM, Sassafras, BEEFY y cómo conectar un consenso BFT propio a un nodo.
Ruta práctica: activos digitales en la operación
Nueve módulos, de menor a mayor dependencia, para decidir cómo usar custodia, wallets, llaves, firmas multifirma y MPC, AML/KYT y stablecoins con Fireblocks, BitGo, Kaleido, Circle y Tether.
Labs para convertir lectura en práctica
Prueba de trabajo y reajuste de dificultad
Laboratorio determinista para medir el costo de la prueba de trabajo y ver cómo el reajuste por altura de bloque estabiliza el tiempo entre bloques.
Consensus benchmark
Banco reproducible para comparar consenso BFT y documentar los trade-offs de HotStuff-2.
Ethereum local: Anvil, wallet y primera transacción
Laboratorio base para levantar una red local, crear cuentas, firmar transacciones y observar receipts sin gastar ETH real.
RPC y Cast: leer Ethereum con lupa
Ejercicios con JSON-RPC y Cast para consultar bloques, nonces, gas, receipts, logs y errores reproducibles.
Sigue leyendo
Ideas conectadas con este artículo
Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.
Ethereum: mapa de lectura antes de tocar Solidity
Ruta completa para aprender Ethereum desde historia y arquitectura hasta EVM, proof-of-stake, L2, seguridad, DeFi y labs reproducibles.
Antes de Ethereum: Bitcoin, scripts y la máquina mundial
Historia inicial de Ethereum: qué problema vio el whitepaper, por qué los contratos generales importaban y qué cambió frente a Bitcoin.
Frontier, Homestead y The DAO: la red aprendió con golpes
De mainnet inicial a The DAO: primeros forks, tensiones sociales y por qué la historia de Ethereum no es solo técnica.
Metropolis a Istanbul: Ethereum madura antes del Merge
Lectura histórica de Byzantium, Constantinople e Istanbul como preparación técnica para una red más segura y escalable.