Recorrido por un contrato ERC-721 de Vyper
Introducción
El estándar ERC-721 se usa para mantener la propiedad de los tokens no fungibles (NFT). Los tókenes ERC-20 se comportan como una mercancía, porque no hay diferencia entre los tókenes individuales. A diferencia de esto, los tókenes ERC-721 están diseñados para activos que son similares pero no idénticos, como diferentes dibujos de gatosopens in a new tab o títulos de diferentes propiedades inmobiliarias.
En este artículo analizaremos el contrato ERC-721 de Ryuya Nakamuraopens in a new tab. Este contrato está escrito en Vyperopens in a new tab, un lenguaje de contratos similar a Python diseñado para que sea más difícil escribir código inseguro que en Solidity.
El contrato
1# @dev Implementación del estándar de token no fungible ERC-721.2# @author Ryuya Nakamura (@nrryuya)3# Modificado de: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vyLos comentarios en Vyper, como en Python, empiezan con un hash (#) y continúan hasta el final de la línea. Los comentarios que incluyen @<keyword> son utilizados por NatSpecopens in a new tab para producir documentación legible por humanos.
1from vyper.interfaces import ERC72123implements: ERC721La interfaz ERC-721 está integrada en el lenguaje Vyper. Puede ver la definición del código aquíopens in a new tab. La definición de la interfaz está escrita en Python, en lugar de Vyper, porque las interfaces se utilizan no solo dentro de la cadena de bloques, sino también al enviar a la cadena de bloques una transacción desde un cliente externo, que puede estar escrito en Python.
La primera línea importa la interfaz y la segunda especifica que la estamos implementando aquí.
La interfaz ERC721Receiver
1# Interfaz para el contrato llamado por safeTransferFrom()2interface ERC721Receiver:3 def onERC721Received(ERC-721 admite dos tipos de transferencia:
transferFrom, que permite al emisor especificar cualquier dirección de destino y pone la responsabilidad de la transferencia sobre el emisor. Esto significa que puede transferir a una dirección no válida, en cuyo caso el NFT se pierde para siempre.safeTransferFrom, que comprueba si la dirección de destino es un contrato. Si es así, el contrato ERC-721 le pregunta al contrato receptor si quiere recibir el NFT.
Para responder a las solicitudes de safeTransferFrom, un contrato receptor tiene que implementar ERC721Receiver.
1 _operator: address,2 _from: address,La dirección _from es el propietario actual del token. La dirección _operator es la que solicitó la transferencia (estas dos pueden no ser las mismas, debido a los permisos).
1 _tokenId: uint256,Los ID del token ERC-721 son de 256 bits. Normalmente, se crean mediante el hash de una descripción de lo que el token representa.
1 _data: Bytes[1024]La solicitud puede tener hasta 1024 bytes de datos de usuario.
1 ) -> bytes32: viewPara evitar casos en los que un contrato acepte accidentalmente una transferencia, el valor de retorno no es un booleano, sino 256 bits con un valor específico.
Esta función es una view, lo que significa que puede leer el estado de la cadena de bloques, pero no modificarlo.
Eventos
Se emiten eventosopens in a new tab para informar de los eventos a los usuarios y servidores que están fuera de la cadena de bloques. Tenga en cuenta que el contenido de los eventos no está disponible para los contratos en la cadena de bloques.
1# @dev Se emite cuando la propiedad de cualquier NFT cambia por cualquier mecanismo. Este evento se emite cuando los NFT se2# crean (`from` == 0) y se destruyen (`to` == 0). Excepción: durante la creación del contrato, se puede3# crear y asignar cualquier número de NFT sin emitir una Transferencia. En el momento de cualquier4# transferencia, la dirección aprobada para ese NFT (si la hubiera) se restablece a ninguna.5# @param _from Emisor del NFT (si la dirección es la dirección cero, indica la creación del token).6# @param _to Receptor del NFT (si la dirección es la dirección cero, indica la destrucción del token).7# @param _tokenId El NFT que fue transferido.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Mostrar todoEsto es similar al evento de Transferencia de ERC-20, con la excepción de que se reporta un tokenId en lugar de una cantidad.
Nadie posee la dirección cero, así que, por convención, la usamos para informar de la creación y la destrucción de los tokens.
1# @dev Se emite cuando la dirección aprobada para un NFT se cambia o se reafirma. La dirección2# cero indica que no hay ninguna dirección aprobada. Cuando se emite un evento de Transferencia, esto también3# indica que la dirección aprobada para ese NFT (si la hubiera) se restablece a ninguna.4# @param _owner Propietario del NFT.5# @param _approved Dirección que estamos aprobando.6# @param _tokenId NFT que estamos aprobando.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Mostrar todoUna aprobación de ERC-721 es similar a una autorización de ERC-20. A una dirección específica se le permite transferir un token específico. Esto proporciona un mecanismo para que los contratos respondan cuando aceptan un token. Los contratos no pueden escuchar eventos, así que si simplemente les transfiere el token, estos no lo "sabrán". De esta manera, el propietario primero envía una aprobación y luego una solicitud al contrato: "Le he aprobado para transferir el token X, por favor, hágalo...".
Esta es una decisión de diseño para hacer que el estándar ERC-721 sea similar al estándar ERC-20. Como los tókenes ERC-721 no son fungibles, un contrato también puede identificar que obtuvo un token específico al mirar la propiedad del token.
1# @dev Se emite cuando se habilita o deshabilita un operador para un propietario. El operador puede gestionar2# todos los NFT del propietario.3# @param _owner Propietario del NFT.4# @param _operator Dirección a la que le estamos asignando los derechos de operador.5# @param _approved Estado de los derechos de operador (verdadero si se otorgan los derechos de operador y falso si6# se revocan).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolMostrar todoA veces es útil tener un operador que pueda gestionar todos los tókenes de una cuenta de un tipo específico (aquellos que son gestionados por un contrato específico), de forma similar a un poder notarial. Por ejemplo, podría querer darle dicho poder a un contrato que compruebe si no lo he contactado durante seis meses y, en ese caso, distribuya mis activos a mis herederos (si uno de ellos lo solicita, los contratos no pueden hacer nada sin ser llamados por una transacción). En ERC-20 podemos simplemente dar una autorización alta a un contrato de herencia, pero eso no funciona para ERC-721 porque los tókenes no son fungibles. Este es el equivalente.
El valor approved nos dice si el evento es para una aprobación o para el retiro de una aprobación.
Variables de estado
Estas variables contienen el estado actual de los tókenes: cuáles están disponibles y quién los posee. La mayoría de estos son objetos HashMap, mapeos unidireccionales que existen entre dos tiposopens in a new tab.
1# @dev Mapeo del ID del NFT a la dirección que lo posee.2idToOwner: HashMap[uint256, address]34# @dev Mapeo del ID del NFT a la dirección aprobada.5idToApprovals: HashMap[uint256, address]Las identidades del usuario y del contrato en Ethereum vienen representadas por direcciones de 160 bits. Estas dos variables mapean desde identificadores de tókenes a sus propietarios y aquellos aprobados para transferirlos (máximo uno para cada uno). En Ethereum, los datos no inicializados siempre son cero, así que si no hay ningún propietario o transferidor aprobado, el valor para ese token es cero.
1# @dev Mapeo de la dirección del propietario al recuento de sus tokens.2ownerToNFTokenCount: HashMap[address, uint256]Esta variable contiene el recuento de tokens de cada propietario. No hay mapeo de propietarios a tókenes, así que la única manera de identificar los tókenes que posee un propietario específico es mirar el historial de eventos de la cadena de bloques y ver los eventos apropiados de Transfer. Podemos usar esta variable para saber cuándo tenemos todos los NFT y no necesitamos buscar más atrás en el tiempo.
Tenga en cuenta que este algoritmo sólo funciona para interfaces de usuario y servidores externos. El código en ejecución en la propia cadena de bloques no puede leer eventos pasados.
1# @dev Mapeo de la dirección del propietario al mapeo de las direcciones del operador.2ownerToOperators: HashMap[address, HashMap[address, bool]]Una cuenta puede tener más de un solo operador. Un HashMap simple es insuficiente para llevar un seguimiento de ellos, porque cada clave conduce a un único valor. En su lugar, puede utilizar HashMap[address, bool] como el valor. Por defecto, el valor para cada dirección es False, lo que significa que no es un operador. Puede establecer los valores a True según sea necesario.
1# @dev Dirección del acuñador, quien puede acuñar un token.2minter: addressLos nuevos tokens tienen que crearse de alguna manera. En este contrato hay una única entidad que puede hacerlo, el minter. Es probable que esto sea suficiente para un juego, por ejemplo. Para otros propósitos, podría ser necesario crear una lógica de negocio más complicada.
1# @dev Mapeo de la id de la interfaz a un bool sobre si es compatible o no.2supportedInterfaces: HashMap[bytes32, bool]34# @dev ID de interfaz ERC165 de ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ID de interfaz ERC165 de ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdERC-165opens in a new tab especifica un mecanismo para que un contrato revele cómo las aplicaciones pueden comunicarse con él y a qué ERC se ajusta. En este caso, el contrato se ajusta a ERC-165 y ERC-721.
Funciones
Estas son las funciones que realmente implementan ERC-721.
Constructor
1@external2def __init__():En Vyper, como en Python, la función constructora se llama __init__.
1 """2 @dev Constructor de contrato.3 """En Python y en Vyper, también puede crear un comentario especificando una cadena de varias líneas (que comienza y termina con """) y no utilizándola de ninguna manera. Estos comentarios también pueden incluir NatSpecopens in a new tab.
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderPara acceder a las variables de estado, use self.<nombre de la variable>(de nuevo, igual que en Python).
Funciones de vista
Estas son funciones que no modifican el estado de la cadena de bloques y, por lo tanto, pueden ejecutarse de forma gratuita si se llaman externamente. Si las funciones de vista son llamadas por un contrato, todavía tienen que ejecutarse en cada nodo y, por lo tanto, cuestan gas.
1@view2@externalEstas palabras clave que preceden a la definición de una función y que empiezan con una arroba (@) se denominan decoraciones. Especifican las circunstancias en las que se puede llamar a una función.
@viewespecifica que esta función es una vista.@externalespecifica que esta función en particular puede ser llamada por transacciones y por otros contratos.
1def supportsInterface(_interfaceID: bytes32) -> bool:A diferencia de Python, Vyper es un lenguaje de tipo estáticoopens in a new tab.
No se puede declarar una variable o un parámetro de función sin identificar el tipo de datosopens in a new tab. En este caso, el parámetro de entrada es bytes32, un valor de 256 bits (256 bits es el tamaño de palabra nativo de la máquina virtual de Ethereum). La salida es un valor booleano. Por convención, los nombres de los parámetros de las funciones comienzan con un guion bajo (_).
1 """2 @dev La identificación de la interfaz se especifica en ERC-165.3 @param _interfaceID Id de la interfaz.4 """5 return self.supportedInterfaces[_interfaceID]Devuelve el valor del HashMap self.supportedInterfaces, que se establece en el constructor (__init__).
1### FUNCIONES DE VISTA ###2Estas son las funciones de vista que hacen que la información sobre los tokens esté disponible para los usuarios y otros contratos.
1@view2@external3def balanceOf(_owner: address) -> uint256:4 """5 @dev Devuelve el número de NFT que posee `_owner`.6 Lanza un error si `_owner` es la dirección cero. Los NFT asignados a la dirección cero se consideran inválidos.7 @param _owner Dirección para la que se consulta el saldo.8 """9 assert _owner != ZERO_ADDRESSMostrar todoEsta línea afirmaopens in a new tab que _owner no es cero. Si lo es, se produce un error y la operación se revierte.
1 return self.ownerToNFTokenCount[_owner]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Devuelve la dirección del propietario del NFT.8 Lanza un error si `_tokenId` no es un NFT válido.9 @param _tokenId El identificador de un NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Lanza un error si `_tokenId` no es un NFT válido.13 assert owner != ZERO_ADDRESS14 return ownerMostrar todoEn la máquina virtual de Ethereum (EVM), cualquier almacenamiento que no tenga un valor almacenado en él es cero.
Si no hay ningún token en _tokenId, entonces el valor de self.idToOwner[_tokenId] es cero. En ese caso, la función se revierte.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Obtener la dirección aprobada para un único NFT.6 Lanza un error si `_tokenId` no es un NFT válido.7 @param _tokenId ID del NFT para consultar la aprobación.8 """9 # Lanza un error si `_tokenId` no es un NFT válido.10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Mostrar todoTenga en cuenta que getApproved puede devolver cero. Si el token es válido, devuelve self.idToApprovals[_tokenId].
Si no hay un aprobador, ese valor es cero.
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev Comprueba si `_operator` es un operador aprobado para `_owner`.6 @param _owner La dirección que posee los NFT.7 @param _operator La dirección que actúa en nombre del propietario.8 """9 return (self.ownerToOperators[_owner])[_operator]Mostrar todoEsta función comprueba si _operator tiene permiso para gestionar todos los tokens de _owner en este contrato.
Debido a que puede haber múltiples operadores, se trata de un HashMap de dos niveles.
Funciones auxiliares de transferencia
Estas funciones implementan operaciones que son parte de la transferencia o la gestión de tókenes.
12### FUNCIONES AUXILIARES DE TRANSFERENCIA ###34@view5@internalEsta decoración, @internal, significa que la función solo es accesible desde otras funciones dentro del mismo contrato. Por convención, los nombres de estas funciones también comienzan con un guion bajo (_).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Devuelve si el gastador dado puede transferir un ID de token dado4 @param spender Dirección del gastador a consultar5 @param tokenId ID de uint256 del token a transferir6 @return bool si el msg.sender está aprobado para el ID de token dado,7 es un operador del propietario, o es el propietario del token.8 """9 owner: address = self.idToOwner[_tokenId]10 spenderIsOwner: bool = owner == _spender11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAllMostrar todoHay tres formas en las que se puede permitir que una dirección transfiera un token:
- La dirección es la propietaria del token.
- La dirección está aprobada para gastar ese token
- La dirección es un operador para el propietario del token.
La función anterior puede ser una vista porque no cambia el estado. Para reducir los costes operativos, cualquier función que pueda ser una vista debería ser una vista.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Añadir un NFT a una dirección determinada5 Lanza un error si `_tokenId` es propiedad de alguien.6 """7 # Lanza un error si `_tokenId` es propiedad de alguien.8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Cambiar el propietario10 self.idToOwner[_tokenId] = _to11 # Cambiar el seguimiento del recuento12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Eliminar un NFT de una dirección determinada.19 Lanza un error si `_from` no es el propietario actual.20 """21 # Lanza un error si `_from` no es el propietario actual.22 assert self.idToOwner[_tokenId] == _from23 # Cambiar el propietario24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Cambiar el seguimiento del recuento26 self.ownerToNFTokenCount[_from] -= 1Mostrar todoCuando hay un problema con una transferencia, revertimos la llamada.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Borrar la aprobación de una dirección determinada.5 Lanza un error si `_owner` no es el propietario actual.6 """7 # Lanza un error si `_owner` no es el propietario actual.8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Restablecer las aprobaciones11 self.idToApprovals[_tokenId] = ZERO_ADDRESSMostrar todoCambie el valor solo si es necesario. Las variables de estado residen en el almacenamiento. Escribir en el almacenamiento es una de las operaciones más caras que realiza la EVM (máquina virtual de Ethereum) (en términos de gas). Por lo tanto, es una buena idea minimizarlo, incluso escribir el valor existente tiene un alto coste.
1@internal2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):3 """4 @dev Ejecutar la transferencia de un NFT.5 Se produce una excepción a menos que `msg.sender` sea el propietario actual, un operador autorizado o la dirección aprobada6 para este NFT. (NOTA: `msg.sender` no está permitido en la función privada, así que se pasa `_sender`).7 Lanza un error si `_to` es la dirección cero.8 Lanza un error si `_from` no es el propietario actual.9 Lanza un error si `_tokenId` no es un NFT válido.10 """Mostrar todoTenemos esta función interna porque hay dos maneras de transferir tokens (regular y segura), pero queremos un solo lugar en el código donde lo hagamos para facilitar la auditoría.
1 # Comprobar los requisitos2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Lanza un error si `_to` es la dirección cero4 assert _to != ZERO_ADDRESS5 # Borrar la aprobación. Lanza un error si `_from` no es el propietario actual6 self._clearApproval(_from, _tokenId)7 # Eliminar NFT. Lanza un error si `_tokenId` no es un NFT válido8 self._removeTokenFrom(_from, _tokenId)9 # Añadir NFT10 self._addTokenTo(_to, _tokenId)11 # Registrar la transferencia12 log Transfer(_from, _to, _tokenId)Mostrar todoPara emitir un evento en Vyper se utiliza una sentencia log (consulte aquí para más detallesopens in a new tab).
Funciones de transferencia
12### FUNCIONES DE TRANSFERENCIA ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Se produce una excepción a menos que `msg.sender` sea el propietario actual, un operador autorizado o la dirección aprobada8 para este NFT.9 Lanza un error si `_from` no es el propietario actual.10 Lanza un error si `_to` es la dirección cero.11 Lanza un error si `_tokenId` no es un NFT válido.12 @notice El llamador es responsable de confirmar que `_to` es capaz de recibir NFT, de lo contrario13 pueden perderse permanentemente.14 @param _from El propietario actual del NFT.15 @param _to El nuevo propietario.16 @param _tokenId El NFT a transferir.17 """18 self._transferFrom(_from, _to, _tokenId, msg.sender)Mostrar todoEsta función le permite transferir a una dirección arbitraria. A menos que la dirección sea un usuario, o un contrato que sepa cómo transferir tokens, cualquier token que transfiera se quedará atascado en esa dirección y será inútil.
1@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 """9 @dev Transfiere la propiedad de un NFT de una dirección a otra.10 Se produce una excepción a menos que `msg.sender` sea el propietario actual, un operador autorizado o la11 dirección aprobada para este NFT.12 Lanza un error si `_from` no es el propietario actual.13 Lanza un error si `_to` es la dirección cero.14 Lanza un error si `_tokenId` no es un NFT válido.15 Si `_to` es un contrato inteligente, llama a `onERC721Received` en `_to` y produce una excepción si16 el valor de retorno no es `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 NOTA: bytes4 está representado por bytes32 con relleno18 @param _from El propietario actual del NFT.19 @param _to El nuevo propietario.20 @param _tokenId El NFT a transferir.21 @param _data Datos adicionales sin formato especificado, enviados en la llamada a `_to`.22 """23 self._transferFrom(_from, _to, _tokenId, msg.sender)Mostrar todoEstá bien hacer la transferencia primero porque si hay un problema vamos a revertir de todos modos, así que todo lo que se haga en la llamada será cancelado.
1 if _to.is_contract: # comprobar si `_to` es una dirección de contratoPrimero compruebe si la dirección es un contrato (si tiene código). Si no, asuma que es una dirección de usuario y que el usuario podrá usar el token o transferirlo. Pero no deje que le arrulle en una falsa sensación de seguridad. Puede perder tokens, incluso con safeTransferFrom, si los transfiere a una dirección de la que nadie conoce la clave privada.
1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)Llame al contrato de destino para ver si puede recibir tókenes ERC-721.
1 # Lanza un error si el destino de la transferencia es un contrato que no implementa 'onERC721Received'.2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)Si el destino es un contrato, pero uno que no acepta tókenes ERC-721 (o que decidió no aceptar esta transferencia en particular), se revierte.
1@external2def approve(_approved: address, _tokenId: uint256):3 """4 @dev Establecer o reafirmar la dirección aprobada para un NFT. La dirección cero indica que no hay dirección aprobada.5 Lanza un error a menos que `msg.sender` sea el propietario actual del NFT, o un operador autorizado del propietario actual.6 Lanza un error si `_tokenId` no es un NFT válido. (NOTA: Esto no está escrito en la EIP)7 Lanza un error si `_approved` es el propietario actual. (NOTA: Esto no está escrito en la EIP)8 @param _approved Dirección a aprobar para el ID de NFT dado.9 @param _tokenId ID del token a aprobar.10 """11 owner: address = self.idToOwner[_tokenId]12 # Lanza un error si `_tokenId` no es un NFT válido.13 assert owner != ZERO_ADDRESS14 # Lanza un error si `_approved` es el propietario actual15 assert _approved != ownerMostrar todoPor convención, si no quiere tener un aprobador, designe la dirección cero, no a usted mismo.
1 # Comprobar los requisitos2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Para establecer una aprobación, puede ser el propietario o un operador autorizado por el propietario.
1 # Establecer la aprobación2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Habilita o deshabilita la aprobación para que un tercero ("operador") gestione todos los10 activos de `msg.sender`. También emite el evento ApprovalForAll.11 Lanza un error si `_operator` es el `msg.sender`. (NOTA: Esto no está escrito en la EIP)12 @notice Esto funciona incluso si el emisor no posee ningún token en ese momento.13 @param _operator Dirección para añadir al conjunto de operadores autorizados.14 @param _approved True si los operadores están aprobados, false para revocar la aprobación.15 """16 # Lanza un error si `_operator` es el `msg.sender`17 assert _operator != msg.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Mostrar todoAcuñar nuevos tokens y destruir los existentes
La cuenta que creó el contrato es el minter, el superusuario que está autorizado a acuñar nuevos NFT. Sin embargo, ni siquiera se le permite quemar los tókenes existentes. Solo el propietario, o una entidad autorizada por el propietario, puede hacerlo.
1### FUNCIONES DE ACUÑACIÓN Y QUEMA ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:Esta función siempre devuelve True, porque si la operación falla, se revierte.
1 """2 @dev Función para acuñar tokens.3 Lanza un error si `msg.sender` no es el acuñador.4 Lanza un error si `_to` es la dirección cero.5 Lanza un error si `_tokenId` es propiedad de alguien.6 @param _to La dirección que recibirá los tokens acuñados.7 @param _tokenId El ID del token a acuñar.8 @return Un booleano que indica si la operación tuvo éxito.9 """10 # Lanza un error si `msg.sender` no es el acuñador11 assert msg.sender == self.minterMostrar todoSolo el acuñador (la cuenta que creó el contrato ERC-721) puede acuñar nuevos tokens. Esto puede ser un problema en el futuro si queremos cambiar la identidad del acuñador. En un contrato de producción, probablemente querrá una función que permita al acuñador transferir los privilegios de acuñador a otra persona.
1 # Lanza un error si `_to` es la dirección cero2 assert _to != ZERO_ADDRESS3 # Añadir NFT. Lanza un error si `_tokenId` es propiedad de alguien4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TruePor convención, la acuñación de nuevos tokens cuenta como una transferencia desde la dirección cero.
12@external3def burn(_tokenId: uint256):4 """5 @dev Quema un token ERC721 específico.6 Lanza un error a menos que `msg.sender` sea el propietario actual, un operador autorizado o la dirección7 aprobada para este NFT.8 Lanza un error si `_tokenId` no es un NFT válido.9 @param _tokenId id uint256 del token ERC721 a quemar.10 """11 # Comprobar los requisitos12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Lanza un error si `_tokenId` no es un NFT válido15 assert owner != ZERO_ADDRESS16 self._clearApproval(owner, _tokenId)17 self._removeTokenFrom(owner, _tokenId)18 log Transfer(owner, ZERO_ADDRESS, _tokenId)Mostrar todoCualquiera que tenga permiso para transferir un token puede quemarlo. Aunque una quema parece equivalente a una transferencia a la dirección cero, la dirección cero no recibe realmente el token. Esto nos permite liberar todo el almacenamiento que se utilizó para el token, lo que puede reducir el costo de gas de la transacción.
Uso de este contrato
A diferencia de Solidity, Vyper no tiene herencia. Esta es una elección de diseño deliberada para hacer el código más claro y, por lo tanto, más fácil de asegurar. Por lo tanto, para crear su propio contrato ERC-721 de Vyper, tome este contratoopens in a new tab y modifíquelo para implementar la lógica de negocio que desee.
Conclusión
A modo de recapitulación, he aquí algunas de las ideas más importantes de este contrato:
- Para recibir tokens ERC-721 con una transferencia segura, los contratos tienen que implementar la interfaz
ERC721Receiver. - Incluso si utiliza una transferencia segura, los tokens pueden atascarse si los envía a una dirección cuya clave privada es desconocida.
- Cuando hay un problema con una operación, es una buena idea
revertirla llamada, en lugar de simplemente devolver un valor de fallo. - Los tokens ERC-721 existen cuando tienen un propietario.
- Existen tres maneras de ser autorizados para transferir un NFT. Puede ser el propietario, estar aprobado para un token específico, o ser un operador para todos los tokens del propietario.
- Los eventos pasados solo son visibles fuera de la cadena de bloques. El código que se ejecuta dentro de la cadena de bloques no puede verlos.
Ahora vaya e implemente contratos seguros de Vyper.
Vea aquí más de mi trabajoopens in a new tab.
Última actualización de la página: 22 de agosto de 2025