paint-brush
Comprensión de los estándares de tokens en Ethereum Parte II (ERC721)por@zartaj
2,489 lecturas
2,489 lecturas

Comprensión de los estándares de tokens en Ethereum Parte II (ERC721)

por Md Zartaj Afser16m2023/03/01
Read on Terminal Reader

Demasiado Largo; Para Leer

Se propuso el estándar ERC-721 para NFT (tokens no fungibles) NFT se refiere a los tokens que son únicos, lo que significa que cada token dentro del contrato inteligente será diferente de los demás. Se puede utilizar para identificar cosas únicas, por ejemplo, algún tipo de imagen, billetes de lotería, coleccionables, pinturas o incluso música, etc.
featured image - Comprensión de los estándares de tokens en Ethereum Parte II (ERC721)
Md Zartaj Afser HackerNoon profile picture
0-item
1-item


Se propuso el estándar ERC-721 para NFT (tokens no fungibles). NFT se refiere a los tokens que son únicos, lo que significa que cada token dentro del contrato inteligente será diferente de los demás. Se puede usar para identificar cosas únicas, por ejemplo, algún tipo de imagen, boletos de lotería, coleccionables, pinturas o incluso música, etc. Para recordarte, ERC-20 era para las fichas fungibles, si no has leído el artículo sobre ERC-20, considere leerlo primero. El mejor proyecto del mundo real para el ejemplo ERC-721 es Cryptokitties . Es un juego en la parte superior de la cadena de bloques, echa un vistazo a este increíble juego para obtener conocimiento con diversión.


Pero, ¿cómo funciona esto? ¿Cómo se almacenan estas imágenes, música y pinturas en contratos inteligentes?


Bueno, estas cosas no se almacenan dentro del contrato, sino que el contrato solo apunta a un activo externo. Hay un número, tokenId que muestra la propiedad. El activo principal es la imagen o algún otro dato que se adjunta al tokenId a través de un URI. Buceemos un poco más.


Asignamos un URI, que es un texto JSON, a la identificación del token. Y ese texto JSON contiene los detalles y la ruta a la imagen o cualquier otro recurso. Y el usuario solo posee el tokenid . Por ejemplo, mi dirección tiene el saldo de tokenid 4, ahora la imagen o cualquier tipo de datos adjuntos a ese tokenid serán míos. Soy dueño de esos datos porque soy dueño del tokenid .


Ahora tenemos que almacenar estas dos cosas, es decir, la imagen y el texto JSON (URI) en algún lugar para que cualquier mercado de NFT pueda obtener la imagen y mostrarla en su sitio web. Podría estar pensando por qué necesitamos URI de metadatos, ¿por qué no asignar directamente el URI de imagen a la identificación del token? Bueno, es posible que recuerde por qué necesitábamos los estándares ERC en primer lugar, sí, para que sea más fácil para las aplicaciones del lado del cliente conectarse con los contratos. De manera similar, los metadatos que escribimos para NFT deben estar en un formato adecuado recomendado por el mercado de NFT para que puedan obtener fácilmente los datos de ese archivo JSON y mostrar los datos en su sitio.


Veamos ahora lo del almacenamiento.

Almacenar los datos

Hay dos formas de almacenar la imagen y el URI. Podemos alojarlo en la cadena (dentro de la cadena de bloques) o fuera de la cadena (fuera de la cadena de bloques).


Los datos en cadena se almacenan en la cadena de bloques, pero ocupan mucho espacio que no es asequible. Solo piense en pagar miles de dólares al momento de implementar su contrato. No suena bien. Aparte de esto, la cadena de bloques también proporciona almacenamiento limitado, no puede almacenar archivos grandes en ella.


Entonces tenemos la opción de alojar los datos fuera de la cadena de bloques. A través de nuestro sitio web utilizando AWS o cualquier otro servicio en la nube. Pero esto mata la idea principal de una cadena de bloques, es decir, la descentralización. ¿Qué sucede si el servidor falla o alguien lo piratea? Ya que habría un único servidor que albergaría todos los datos. Entonces, necesitamos algo más que sea resistente a los ataques y también descentralizado, y ese algo es IPFS (Sistema de archivos interplanetarios), que es un sistema de almacenamiento distribuido. IPFS tiene múltiples nodos similares a la idea de blockchain que almacena los datos. Pero no tienes que pagar ninguna tarifa. Podemos alojar nuestras imágenes y también el archivo JSON (URI) en IPFS y, al hacerlo, le daremos un CID único (galimatías aleatorias) que apunta directamente a los datos y asignamos un CID particular a la identificación del token en particular. Hablaremos más sobre la parte técnica más adelante, primero entendamos la interfaz para el estándar ERC-721.

INTERFAZ ERC-721

Veamos cómo se ve la interfaz ERC-721.

Este estándar tiene las siguientes funcionalidades:

  1. Transferir tokens de una cuenta a otra.

  2. Obtener el saldo actual de tokens de una cuenta.

  3. Obtener el propietario de un token específico.

  4. El suministro total del token disponible en la red.

  5. Además de estos, también tiene algunas otras funcionalidades, como aprobar que una cuenta de un tercero pueda mover una cantidad de token de una cuenta.


Ahora echemos un vistazo a la interfaz para ERC-721.

 pragma solidity ^0.4.20; interface ERC721 { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool); } Break

Descomponer

  1. balanceOf Devuelve el número de tokens en la cuenta del propietario. ¿Cómo hacemos un seguimiento de los saldos? Bueno, siempre es lo mismo, definimos un mapeo que rastrea la cantidad total de tokens en una billetera. mapeo(dirección => uint) saldos públicos; Recuerde, solo suma la cantidad total de tokens y devuelve lo mismo, esta asignación no conoce a los propietarios del token, ya que solo indica la cantidad de identificadores de token que tiene una dirección.

function balanceOf(address _owner) external view returns (uint256);


2. ownOf Encuentra el propietario de un NFT. Ahora, esta función nos informa sobre el propietario de un NFT en particular. Mantenemos estos datos similares a los anteriores. mapeo( uint => dirección) saldos públicos; Los NFT asignados a direcciones cero se consideran no válidos y las consultas sobre ellos deberían arrojar un error.

function ownerOf(uint256 _tokenId) external view returns (address);


3. safeTransferFrom Transfiere la propiedad de un NFT de una dirección a otra. Simplemente debe establecer el propietario de un tokenId en la nueva dirección y también actualizar la asignación balances . Debería arrojar un error en los siguientes casos:


  • msg.sender es el propietario actual, un operador autorizado o la dirección aprobada para este NFT
  • _from no es el propietario actual
  • __to_ es la dirección cero.
  • __tokenId_ no es un NFT válido


Cuando se completa la transferencia, esta función verifica si __to_ es un contrato inteligente (tamaño de código> 0). Si es así, llama a la función onERC721Received en __to_ y lanza si el valor de retorno no es igual a esto: bytes4(keccak256("onERC721Received(address, address,uint256, bytes)")).


¿Qué era? Es por eso que la función se llama transferencia segura, hablaremos más sobre esto más adelante.


function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;


4. safeTransferFrom : funciona de manera idéntica a la función anterior con un parámetro de datos adicional, excepto que esta función solo establece los datos en " ".


function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;




¿Cómo es que estas dos funciones tienen el mismo nombre?

Dato curioso: a diferencia de Javascript, Solidity admite la sobrecarga de funciones. significa que podemos definir dos funciones con los mismos nombres, la única condición es que los parámetros sean diferentes, haciendo una diferencia en la firma de la función. ¿No sabes qué son las firmas de funciones? Aquí tienes. Enlace para responder


5. transferFrom transfiere la propiedad de un NFT. Funciona igual que la función safeTransferFrom , la única diferencia es que no llama a la llamada de seguridad ( onERC721Received ) en el destinatario. Por lo tanto, crea un riesgo de perder el NFT.


function transferFrom(address _from, address _to, uint256 _tokenId) external payable;



6. aprobar el mismo concepto que la función de aprobación ERC20 pero con más funcionalidad y lógica. Para que esta función funcione, definimos un mapeo anidado: mapping(uint =>address) internal allowance; En este mapeo, el uint se refiere a la identificación del token y la dirección que está aprobada para realizar operaciones en ese tokenId en particular. Esta función de aprobación debe verificar que msg.sender sea el propietario actual o un operador del propietario del token. Si se aprueba esta verificación, solo se debe actualizar la asignación. ¿Cuál es el operador aquí? Ver la siguiente función.

function approve(address _approved, uint256 _tokenId) external payable;




7. setApprovalForAll funciona como la función de aprobación anterior, pero en lugar de aprobar una dirección para una sola identificación de token, esta función debe aprobar y dirigir para manejar todos los tokens que pertenecen a una dirección en particular. ¿Qué significa? Significa que puede crear un operador de dirección para sus NFT. Esa dirección será la propietaria de sus NFT hasta que revoque la propiedad. Esta vez usamos nuevamente el mapeo: mapping(address => mapping(address=>bool))internal operator; La primera dirección establece el booleano verdadero o falso para que la segunda dirección apruebe o revoque, respectivamente.


function setApprovalForAll(address _operator, bool _approved) external;


8. getApproved es similar a la función de asignación en la interfaz ERC-20. Devuelve la address aprobada para un solo NFT. Comprueba la asignación que actualizamos en la función de aprobación anterior.


function getApproved(uint256 _tokenId) external view returns (address);


9. isApprovedForAll similar a la función anterior. Consulta si una address es operator autorizado para otra address . En lugar de devolver la address aprobada de un tokenId en particular, esta función debería devolver la address del operator para la address dada que actualizamos en la función setApprovalForAll

function isApprovedForAll(address _owner, address _operator) external view returns (bool);

Eventos para ERC-721

Transferencia: se emite cuando la propiedad de cualquier NFT cambia por cualquier mecanismo. Este evento se emite cuando se crean NFT (from == 0) y se destruyen (to == 0). Excepción : durante la creación del contrato, se puede crear y asignar cualquier número de NFT sin emitir Transferencia. En el momento de cualquier transferencia, la dirección aprobada para ese NFT (si corresponde) se restablece a ninguna.


event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);


La aprobación se emite cuando se cambia o reafirma la dirección aprobada para un NFT. La dirección cero indica que no hay una dirección aprobada. Cuando se emite un evento Transfer, esto también indica que la dirección aprobada para ese NFT (si corresponde) se restablece a ninguno.


event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);


ApproveForAll se emite cuando un operador está habilitado o deshabilitado para un propietario. El operador puede gestionar todos los NFT del propietario.


event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);



Esta fue la interfaz para los contratos ERC 721. Ahora aprendamos más sobre sus implementaciones y reglas.

ERC721TokenReceiver


Antes de la implementación, hay una cosa más. ¿Recuerdas que hablamos de una función llamada onERC721Received cuando hablamos de transferir tokens? Hablemos de eso ahora. Si el receptor de un token ERC-721 es un contrato, entonces debe funcionar con los tokens ERC-721. Porque, ¿qué pasa si no hay funcionalidad para interactuar con los tokens en ese contrato de receptor? Los tokens quedarán bloqueados en ese contrato para siempre. Nadie puede llevar esos tokens a ninguna dirección ya que no hay funcionalidad. Para superar esta vulnerabilidad, el receptor del token ERC-721 debe implementar la interfaz del receptor; de lo contrario, ninguno de los contratos puede enviar ningún token ERC-721 a ese contrato. Veamos la interfaz del receptor.


Esta interfaz contiene solo una función para implementar y es onERC721Received, esta función debe manejar la recepción de un NFT. El contrato inteligente ERC-721 llama a esta función en el destinatario después de una transfer . Esta función PUEDE lanzar para revertir y rechazar la transferencia. Las devoluciones que no sean el valor mágico DEBEN resultar en la reversión de la transacción.


 interface ERC721TokenReceiver { function onERC721Received(address _operator,address _from,uint256 _tokenId,bytes _data) external returns(bytes4); }


Aquí podría estar pensando cómo sabría el contrato de token si el receptor ha implementado la interfaz ERC721TokenReceiver o no. Entonces, antes de enviar los tokens, primero debe verificar esta parte. Para esto, veamos la implementación openzeppelin de ERC-721. Aquí está la función privada a la que debe llamar dentro de safeTransfer() antes de enviar los tokens. Si esto devuelve verdadero, entonces solo debería ocurrir la transacción.



Bueno, esto no garantiza la funcionalidad de sacar los NFT del contrato del receptor porque solo verifica la implementación de la función del receptor, pero no la funcionalidad de transferir los NFT del contrato. Entonces, ¿cuál es el significado de esto? Este método puede decirnos que el autor del contrato del receptor estaba al menos al tanto de este método, lo que significa que debe haber implementado la funcionalidad para transferir los NFT fuera del contrato. Y, por supuesto, nadie querría que sus tokens se quedaran atrapados dentro de un contrato.

ERC721Metadatos y ERC721Enumerable

Las interfaces anteriores eran obligatorias para un contrato de token ERC-721. Ahora, algunas interfaces son teóricamente opcionales pero hacen que el contrato sea más claro y más utilizable.


Estos son ERC721Metadata y ERC721Enumerable. Vamos a atraparlos uno por uno.


  1. Metadatos ERC721: la extensión de metadatos se utiliza principalmente para interrogar el contrato por el nombre del token. Ahora veamos la interfaz de metadatos. Esto es bastante simple de entender.
 interface ERC721Metadata/* is ERC721 */ { /// @notice A descriptive name for a collection of NFTs in this /// contract function name()external view returns (string _name); /// @notice An abbreviated name for NFTs in this contract function symbol()external view returns (string _symbol); /// @notice A distinct Uniform Resource Identifier (URI) for a given /// asset. /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined /// in RFC3986. The URI may point to a JSON file that conforms to /// the "ERC721Metadata JSON Schema". function tokenURI(uint256 _tokenId)external view returns (string);


  1. ERC721Enumerable : esto solo proporciona los datos sobre los NFT.

     interface ERC721Enumerable/* is ERC721 */ { /// @notice Count NFTs tracked by this contract /// @return A count of valid NFTs tracked by this contract, where /// each one of them has an assigned and queryable owner not equal /// to the zero address function totalSupply()external view returns (uint256); /// @notice Enumerate valid NFTs /// @dev Throws if `_index` >= `totalSupply()`. /// @param _index A counter less than `totalSupply()` /// @return The token identifier for the `_index`th NFT, /// (sort order not specified) function tokenByIndex(uint256 _index)external view returns (uint256); /// @notice Enumerate NFTs assigned to an owner /// @dev Throws if `_index` >= `balanceOf(_owner)` or if /// `_owner` is the zero address, representing invalid NFTs. /// @param _owner An address where we are interested in NFTs owned /// by them /// @param _index A counter less than `balanceOf(_owner)` /// @return The token identifier for the `_index`th NFT assigned to /// `_owner`, /// (sort order not specified) function tokenOfOwnerByIndex(address _owner,uint256 _index)external view returns(uint256);

ERC165

Ahora, ¿cómo sabríamos si una interfaz ha sido implementada por un contrato o no? Para esto, debemos desviarnos del tema, es decir , ERC-165, que tiene una función:

function supportsInterface(bytes4 interfaceID) external view returns (bool);


Esta función toma el ID de interfaz que desea verificar como argumento y lo compara con el ID de interfaz con el que desea verificar. Te diré qué es un ID de interfaz, ten paciencia conmigo. Primero observe la implementación de esta función por parte de openzeppelin.

Entendamos más sobre interfaceId en este type(IERC721).interfaceId ;



interfaceID se logra mediante dos operaciones principales.

1. keccak 256 hachís

2. Operación XOR


keccak256 es un algoritmo que toma una entrada y escupe una cadena aleatoria en bytes. Ahora averigüemos el hash keccak para esta función.

function supportsInterface(bytes4 interfaceID) external view returns (bool);

La sintaxis se parece a esto.

bytes4(keccak256('supportsInterface(bytes4)') ;



Ahora tenemos el hash keccak para esta función. Recuerde, no tiene que pasar toda la función dentro del parámetro para keccak256 sino solo la firma. La firma significa solo el nombre y el tipo de parámetro, ni siquiera se incluye el nombre del parámetro. Después de obtener el hash keccak256 de todas las funciones, realizamos la operación XOR entre ellas y el resultado que obtenemos es el interfaceId. Veamos cómo. Las operaciones XOR toman entradas y dan algunas salidas después de compararlas. Da como resultado true si una, y solo una, de las entradas es verdadera. Si ambas entradas son falsas o ambas son verdaderas, la salida resulta ser false .


No necesita entender las matemáticas detrás de XOR. Solo recuerde, toma el hash keccak256 de cada función y lo pasa a la puerta XOR y el valor que obtiene es el ID de interfaz. Entonces, la idea principal es tomar el hash keccak de las funciones y obtener su salida XOR. esa salida es el ID de interfaz y después de eso, puede verificar un contrato si tiene el mismo ID de interfaz. En caso afirmativo, eso significa que el contrato está implementando la interfaz deseada.

No te preocupes, solidity te lo ha puesto fácil. Aprendamos tomando la interfaz para ERC721Metadata como ejemplo.


 interface ERC721Metadata/* is ERC721 */ { function name()external view returns (string _name); function symbol()external view returns (string _symbol); function tokenURI(uint256 _tokenId)external view returns (string); }


Aquí tenemos tres funciones. Así que he escrito este fragmento de código para que lo entiendas. Observe aquí el símbolo de intercalación ^ . Este símbolo representa la operación XOR entre dos valores.

 // SPDX-License-Identifier: MIT pragma solidity ^0.8.16 ; interface metadata { function name()external view returns (string memory _name ); function symbol()external view returns (string memory _symbol); function tokenURI(uint256 _tokenId)external view returns (string memory a); } contract { //old and lengthy way by applying the keccak256 algorithm and performing XOR. function getHashOldWay ()public pure returns(bytes4){ return bytes4(keccak256('name()')) ^ bytes4(keccak256('symbol()'))^ bytes4(keccak256('tokenURI(uint256)')); } //Improved way function getHashNewWay ()public pure returns(bytes4){ metadata a; return a.name.selector ^ a.symbol.selector ^ a.tokenURI.selector; } //this is the latest simplest way to get the interfaceId function getHashLatestWay ()public pure returns(bytes4){ return type(metadata).interfaceId; } }


Ohh, nos metimos demasiado en el ERC165 que nos olvidamos del ERC721, volvamos a eso.

¿Cómo obtiene opensea (o cualquier otro mercado) datos del contrato inteligente?


Este es el momento adecuado para comprender los URI de los tokens que ayudan a los diversos mercados de NFT a identificar los datos que ha asignado con un tokenId en particular. Como Opensea es el mercado más famoso, lo tomaremos como ejemplo. Opensea espera que su contrato ERC721 devuelva un tokenURI llamando a la función tokenURI() . Veamos el contrato de Openzeppelin para esto.

Los TokenURI pueden ser de dos tipos.


1. Mismo URI base para todos los tokens, que varía según el tokenId.

De esta manera, asignamos un URI base para todos los tokens y luego le concatenamos el tokenId, obteniendo el URI de metadatos. Esto solo es posible cuando ha asignado su colección de manera que el enlace sea el mismo para cada archivo JSON de metadatos, la única diferencia sería el tokenId. Por ejemplo,

https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/ Este enlace apunta a la carpeta, lo que significa que este es el URI base.

Para obtener un solo metadato, debemos ir a https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/1.json

Y ahora debe devolver el tokenURI del contrato de manera que vaya directamente a los metadatos de ese token en particular, y el mercado pueda obtenerlo. Lo hacemos concatenando el baseURI con el tokenId y codificándolo con abi. Mira esta función a continuación.


 function tokenURI(uint tokenId) override public view returns(string memory) { return (string(abi.encodePacked( "https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/",Strings.toString(tokenId),".json")) ); }

Esta es la función que llamará el mercado para obtener el URI y mostrar todos los datos que hemos proporcionado en el archivo JSON.



2. diferentes URI para cada token.

Esta es otra forma en la que podemos asignar URI que se encuentran en diferentes ubicaciones. Y el enlace IPFS para un archivo individual se ve así. https://ipfs.filebase.io/ipfs/Qma65D75em77UTgP5TYXT4ZK5Scwv9ZaNyJPdX9DNCXRWc De esta manera, no puede simplemente concatenar el tokenId con baseURI para obtener este enlace, sino que este enlace debe adjuntarse directamente al tokenId en la cadena. Y para eso, mire el contrato ERC721URIStorage anterior donde estamos definiendo un mapeo privado para asignar un URI a un tokenId en particular. Y luego también definiendo una función que llena el mapeo con un URI dado. Y la función tokenURI anula la función principal al agregar una nueva funcionalidad para devolver el URI asignado.

Una cosa más a tener en cuenta es el formato del esquema JSON. El JSON debe contener los datos de forma estandarizada para que el mercado pueda obtener los datos deseados y mostrarlos.


El esquema JSON estándar para ERC721.

 { "title": "Asset Metadata", "type": "object", "properties": { "name": { "type": "string", "description": "Identifies the asset to which this NFT represents" }, "description": { "type": "string", "description": "Describes the asset to which this NFT represents" }, "image": { "type": "string", "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." } } }


Estas son las cosas que debe saber si está interesado en el desarrollo de contratos inteligentes ERC-721 (NFT).



Problemas :

Hay varios problemas con la escalabilidad en el estándar ERC721. El problema viene a la hora de transferir los tokens porque el ERC-721 te permite transferir un token a la vez. Esto significa que si desea enviar a alguien 5 NFT, deberá realizar 5 transacciones. Esto es bastante claro para usted cuánto espacio ocuparía en la cadena de bloques. Esta es la razón por la cual en las corridas de toros la red enfrenta problemas, ya que hay demasiada prisa en la red y las tarifas del gas aumentan rápidamente.


ERC-1155 resuelve estos problemas. ¿Cómo? Hablaremos de esto en un post aparte. Si ha entendido completamente el estándar ERC721, entonces ERC1155 no será muy difícil de entender.


También publicado aquí .