Ir al contenido principal

Una explicación del contrato ERC-721

vyperERC-721python
Principiante
Ori Pomerantz
1 de abril de 2021
21 minuto leído minute read

Introducción

La norma ERC-721 sirve para mantener la propiedad de los tókenes no fungibles (o NFT). Los tókenes ERC-20 actúan como mercancía, porque no hay diferencia entre tókenes individuales. En contraste, los tókenes ERC-721 están diseñados para activos similares, pero no identicos, tales como catcartoons(opens in a new tab) o títulos a diferentes piezas de bienes inmuebles.

En este artículo analizaremos el contrato ERC-721 de Ryuya Nakamura(opens in a new tab). Este contrato está escrito en Vyper(opens in a new tab), un lenguaje de contrato similar a Python diseñado para hacer más difícil escribir código inseguro que en Solidity.

El contrato

1# @dev Implementation of ERC-721 non-fungible token standard.
2# @author Ryuya Nakamura (@nrryuya)
3# Modified from: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Copiar

Comentarios en Vyper, como en Python, empiezan con un hash (#) y continúan hasta el final de la línea. Comentarios que incluyen @<keyword> los usan NatSpec(opens in a new tab) para producir documentación legible.

1from vyper.interfaces import ERC721
2
3implements: ERC721
Copiar

La 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 que 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 de receptor ERC721

1# Interface for the contract called by safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(
Copiar

ERC-721 soporta dos tipos de transferencia:

  • transferFrom, que permite al remitente especificar cualquier dirección de destino y responsabiliza al remitente de la transferencia. 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 safeTransferFrom, se solicitará un contrato receptor que implemente ERC721Receiver.

1 _operator: address,
2 _from: address,
Copiar

La dirección _from es el dueño actual del token. La dirección _operador es la que solicitó la transferencia (estos dos no pueden ser los mismos, debido a permisos).

1 _tokenId: uint256,
Copiar

Las ID del token ERC-721 son de 256 bits. Normalmente se crean al cifrar una descripción de lo que sea representara el token.

1 _data: Bytes[1024]
Copiar

La solicitud puede tener hasta 1.024 bytes de datos de usuario.

1 ) -> bytes32: view
Copiar

Para prevenir casos en los que un contrato acepte accidentalmente una transferencia, el valor de retorno no es un booleano, si no 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 modificarla.

Eventos

Los eventos(opens in a new tab) se emiten para informar a los usuarios y servidores fuera de la cadena de bloques de eventos. Tenga en cuenta que el contenido de los eventos no está disponible para los contratos en la cadena de bloques.

1# @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are
2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any
3# number of NFTs may be created and assigned without emitting Transfer. At the time of any
4# transfer, the approved address for that NFT (if any) is reset to none.
5# @param _from Sender of NFT (if address is zero address it indicates token creation).
6# @param _to Receiver of NFT (if address is zero address it indicates token destruction).
7# @param _tokenId The NFT that got transferred.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Mostrar todo
Copiar

Esto es similar a una transferencia de ERC-20, excepto que informamos de un tokenId en lugar de una cantidad. Nadie tiene la dirección cero, así que convencionalmente la utilizamos para informar de la creación y destrucción de tókenes .

1# @dev This emits when the approved address for an NFT is changed or reaffirmed. The zero
2# address indicates there is no approved address. When a Transfer event emits, this also
3# indicates that the approved address for that NFT (if any) is reset to none.
4# @param _owner Owner of NFT.
5# @param _approved Address that we are approving.
6# @param _tokenId NFT which we are approving.
7event Approval:
8 owner: indexed(address)
9 approved: indexed(address)
10 tokenId: indexed(uint256)
Mostrar todo
Copiar

La aprobación de una norma ERC-721 es similar a una autorización para el ERC. Se permite una dirección específica para transferir un token específico. Esto da un mecanismo para que los contratos respondan cuando aceptan un token. Los contratos no pueden escuchar eventos, así que si solo transfiere el token a ellos, estos no lo sabrán. De esta manera el propietario de envía primero una aprobación y luego envía una solicitud al contrato: «He aprobado la transferencia del token X, hágalo por favor...».

Esta es una opción de diseño para hacer que la norma ERC-721 sea similar la norma ERC-20. Debido a que los tókenes ERC-721 no son fungibles, un contrato también puede identificar que obtuvo un token específico mirando la propiedad del token.

1# @dev This emits when an operator is enabled or disabled for an owner. The operator can manage
2# all NFTs of the owner.
3# @param _owner Owner of NFT.
4# @param _operator Address to which we are setting operator rights.
5# @param _approved Status of operator rights(true if operator rights are given and false if
6# revoked).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Mostrar todo
Copiar

A veces es útil tener un operator que pueda administrar todos los tókenes de una cuenta de un tipo específico (aquellos que administra un contrato específico), similar a un poder notarial. Por ejemplo, querría dar tal poder a un contrato que comprueba si no me he puesto en contacto con él durante seis meses. Y si así lo desea, distribuye mis activos a mis herederos (si uno de ellos lo solicita, los contratos no pueden hacer nada sin ser invocados por una transacción). En ERC-20 sólo podemos dar una alta asignación 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 la retirada 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 de HashMap, , asignaciones unidireccionales que existen entre dos tipos(opens in a new tab).

1# @dev Mapping from NFT ID to the address that owns it.
2idToOwner: HashMap[uint256, address]
3
4# @dev Mapping from NFT ID to approved address.
5idToApprovals: HashMap[uint256, address]
Copiar

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 Mapping from owner address to count of his tokens.
2ownerToNFTokenCount: HashMap[address, uint256]
Copiar

Esta variable tiene el recuento de tókenes para 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 cuando tenemos todos los NFT y no necesitamos mirar aún más a 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 cadena de bloques no puede leer eventos pasados.

1# @dev Mapping from owner address to mapping of operator addresses.
2ownerToOperators: HashMap[address, HashMap[address, bool]]
Copiar

Una cuenta puede tener más de un operador único. Un simple HashMap 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 valores a True según sea necesario.

1# @dev Address of minter, who can mint a token
2minter: address
Copiar

Hay que crear nuevos tókenes 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 Mapping of interface id to bool about whether or not it's supported
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ERC165 interface ID of ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ERC165 interface ID of ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
Copiar

ERC-165(opens in a new tab) especifica un mecanismo para un contrato para divulgar cómo las aplicaciones pueden comunicarse con él, con qué ERC concuerda. En este caso, el contrato se ajusta a ERC-165 y ERC-721.

Funciones

Estas son las funciones que implementan ERC-721.

El constructor

1@external
2def __init__():
Copiar

En Vyper, como en Python, la función constructora se llama __init__.

1 """
2 @dev Contract constructor.
3 """
Copiar

En Python, y en Vyper, también puedes crear un comentario especificando una cadena multilínea (que comienza y termina en """), y no usarla de ninguna manera. Estos comentarios también pueden incluir NatSpec(opens in a new tab).

1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender
Copiar

Para acceder a variables de estado se usa self.<nombre de variable> (otra vez, igual que en Python).

Funciones View

Estas son funciones que no modifican el estado de la cadena de bloques y que, por lo tanto, pueden ejecutarse libremente si se invocan externamente. Si las funciones de vista (View) se invocan mediante un contrato y todavía tienen que ejecutarse en cada nodo y por lo tanto cuestan gas.

1@view
2@external
Copiar

Estas palabras clave antes de una definición de función que empiezan con un signo en la pantalla (@) se llaman decoraciones. Y especifican las circunstancias en las que se puede activar una función.

  • @view especifica que esta función es una vista.
  • @external especifica que esta función en concreto puede activarse por transacciones y por otros contratos.
1def supportsInterface(_interfaceID: bytes32) -> bool:
Copiar

A diferencia de Python, Vyper es un lenguaje tipeado estático(opens in a new tab). No se puede declarar una variable o un parámetro de función sin identificar el tipo de datos(opens in a new tab). En este caso, el parámetro de entrada es bytes32, un valor de 256 bits (que es el tamaño de la palabra nativa de la máquina virtual de Ethereum). La salida es un valor booleano. Por costumbre, los nombres de los parámetros de función comienzan por un guión bajo (_).

1 """
2 @dev Interface identification is specified in ERC-165.
3 @param _interfaceID Id of the interface
4 """
5 return self.supportedInterfaces[_interfaceID]
Copiar

Devuelve el valor del HashMap self.supportedInterfaces, que se establece en el constructor (__init__).

1### VIEW FUNCTIONS ###
Copiar

Estas son las funciones de vista que hacen que la información sobre los tókenes esté disponible para los usuarios y otros contratos.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev Returns the number of NFTs owned by `_owner`.
6 Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
7 @param _owner Address for whom to query the balance.
8 """
9 assert _owner != ZERO_ADDRESS
Mostrar todo
Copiar

Esta línea verifica(opens in a new tab) que _owner no es cero. Si lo es, hay un error y el funcionamiento se revierte.

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 """
7 @dev Returns the address of the owner of the NFT.
8 Throws if `_tokenId` is not a valid NFT.
9 @param _tokenId The identifier for an NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Throws if `_tokenId` is not a valid NFT
13 assert owner != ZERO_ADDRESS
14 return owner
Mostrar todo
Copiar

En la máquina virtual de Ethereum (EVM) cualquier almacenamiento que no tenga un valor almacenado en ella 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@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev Get the approved address for a single NFT.
6 Throws if `_tokenId` is not a valid NFT.
7 @param _tokenId ID of the NFT to query the approval of.
8 """
9 # Throws if `_tokenId` is not a valid NFT
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
Mostrar todo
Copiar

Tenga en cuenta que getApproved puede dar cero como valor. Si el token es válido, aparece self.idToApprovals[_tokenId]. Si no hay aprobador, ese valor es cero.

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev Checks if `_operator` is an approved operator for `_owner`.
6 @param _owner The address that owns the NFTs.
7 @param _operator The address that acts on behalf of the owner.
8 """
9 return (self.ownerToOperators[_owner])[_operator]
Mostrar todo
Copiar

Esta función verifica si _operator tiene permitido administrar todos los tókenes de _owner de 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.

1
2### TRANSFER FUNCTION HELPERS ###
3
4@view
5@internal
Copiar

Esta decoración @internal significa que solo se puede accedeer a la función desde otras funciones dentro del mismo contrato. Por costumbre, los nombres de los parámetros de función comienzan por un guión bajo (_).

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev Returns whether the given spender can transfer a given token ID
4 @param spender address of the spender to query
5 @param tokenId uint256 ID of the token to be transferred
6 @return bool whether the msg.sender is approved for the given token ID,
7 is an operator of the owner, or is the owner of the token
8 """
9 owner: address = self.idToOwner[_tokenId]
10 spenderIsOwner: bool = owner == _spender
11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Mostrar todo
Copiar

Hay tres formas en las que se puede permitir que una dirección transfiera un token:

  1. La dirección es el dueño del token.
  2. La dirección está aprobada para gastar ese token.
  3. 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 costos operativos, cualquier función que pueda ser una vista debería ser una vista.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Add a NFT to a given address
5 Throws if `_tokenId` is owned by someone.
6 """
7 # Throws if `_tokenId` is owned by someone
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Change the owner
10 self.idToOwner[_tokenId] = _to
11 # Change count tracking
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Remove a NFT from a given address
19 Throws if `_from` is not the current owner.
20 """
21 # Throws if `_from` is not the current owner
22 assert self.idToOwner[_tokenId] == _from
23 # Change the owner
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Change count tracking
26 self.ownerToNFTokenCount[_from] -= 1
Mostrar todo
Copiar

Cuando hay un problema con una transferencia se revierte la activación.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Clear an approval of a given address
5 Throws if `_owner` is not the current owner.
6 """
7 # Throws if `_owner` is not the current owner
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Reset approvals
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Mostrar todo
Copiar

Cambie el valor solo si es necesario. Las variables de estado viven en el storage. Escribir al almacenamiento es una de las operaciones más caras que hace la EVM (máquina virtual de Ethereum) (en términos de gas). Por lo tanto, es una buena idea minimizarlo, incluso si escribir el valor existente tiene un alto coste.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Execute transfer of a NFT.
5 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
6 address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)
7 Throws if `_to` is the zero address.
8 Throws if `_from` is not the current owner.
9 Throws if `_tokenId` is not a valid NFT.
10 """
Mostrar todo
Copiar

Tenemos esta función interna, porque hay dos maneras de transferir tókenes (regulares y seguros), pero solo queremos una única ubicación en el código donde lo hacemos para simplificar la auditoría.

1 # Check requirements
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Throws if `_to` is the zero address
4 assert _to != ZERO_ADDRESS
5 # Clear approval. Throws if `_from` is not the current owner
6 self._clearApproval(_from, _tokenId)
7 # Remove NFT. Throws if `_tokenId` is not a valid NFT
8 self._removeTokenFrom(_from, _tokenId)
9 # Add NFT
10 self._addTokenTo(_to, _tokenId)
11 # Log the transfer
12 log Transfer(_from, _to, _tokenId)
Mostrar todo
Copiar

Para emitir un evento en Vyper, utiliza una instrucción de registro log (ver aquí para más detalles(opens in a new tab)).

Funciones de transferencia

1
2### TRANSFER FUNCTIONS ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
8 address for this NFT.
9 Throws if `_from` is not the current owner.
10 Throws if `_to` is the zero address.
11 Throws if `_tokenId` is not a valid NFT.
12 @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
13 they maybe be permanently lost.
14 @param _from The current owner of the NFT.
15 @param _to The new owner.
16 @param _tokenId The NFT to transfer.
17 """
18 self._transferFrom(_from, _to, _tokenId, msg.sender)
Mostrar todo
Copiar

Esta 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 tókenes, cualquier token que usted transfiera se quedará atascado en esa dirección e inutilizable.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev Transfers the ownership of an NFT from one address to another address.
10 Throws unless `msg.sender` is the current owner, an authorized operator, or the
11 approved address for this NFT.
12 Throws if `_from` is not the current owner.
13 Throws if `_to` is the zero address.
14 Throws if `_tokenId` is not a valid NFT.
15 If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 NOTE: bytes4 is represented by bytes32 with padding
18 @param _from The current owner of the NFT.
19 @param _to The new owner.
20 @param _tokenId The NFT to transfer.
21 @param _data Additional data with no specified format, sent in call to `_to`.
22 """
23 self._transferFrom(_from, _to, _tokenId, msg.sender)
Mostrar todo
Copiar

Es mejor hacer la transferencia primero, porque si hay un problema lo revertiremos de todos modos, para que todas las operaciones de la activación se cancelen.

1 if _to.is_contract: # check if `_to` is a contract address
Copiar

Primero compruebe si la dirección es un contrato (si tiene código). De lo contrario, asuma que es una dirección de usuario y que el usuario podrá usar el token o transferirlo. No obstante, no se confíe con una falsa sensación de seguridad. Puede perder tókenes, incluso con safeTransferFrom, si los transfiere a una dirección cuya clave privada nadie conozca.

1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
Copiar

Invoque al contrato de destino para ver si puede recibir tókenes ERC-721.

1 # Throws if transfer destination is a contract which does not implement 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
Copiar

Si el destino es un contrato, pero no acepta tókenes ERC-721 (o que decidió no aceptar esta transferencia en particular), reviertalo.

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
5 Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
6 Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
7 Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
8 @param _approved Address to be approved for the given NFT ID.
9 @param _tokenId ID of the token to be approved.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Throws if `_tokenId` is not a valid NFT
13 assert owner != ZERO_ADDRESS
14 # Throws if `_approved` is the current owner
15 assert _approved != owner
Mostrar todo
Copiar

Por lo general, si usted no quiere un aprobador, nombre la dirección cero, no a usted mismo.

1 # Check requirements
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)
Copiar

Para establecer una aprobación usted puede ser el propietario, o un operador autorizado por el propietario.

1 # Set the approval
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Enables or disables approval for a third party ("operator") to manage all of
10 `msg.sender`'s assets. It also emits the ApprovalForAll event.
11 Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
12 @notice This works even if sender doesn't own any tokens at the time.
13 @param _operator Address to add to the set of authorized operators.
14 @param _approved True if the operators is approved, false to revoke approval.
15 """
16 # Throws if `_operator` is the `msg.sender`
17 assert _operator != msg.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
Mostrar todo
Copiar

Acuñar nuevos tokens y destruir las existentes

La cuenta que creó el contrato es el minter, el súper usuario 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### MINT & BURN FUNCTIONS ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:
Copiar

Esta función siempre muestra True, porque si la operación falla se revierte.

1 """
2 @dev Function to mint tokens
3 Throws if `msg.sender` is not the minter.
4 Throws if `_to` is zero address.
5 Throws if `_tokenId` is owned by someone.
6 @param _to The address that will receive the minted tokens.
7 @param _tokenId The token id to mint.
8 @return A boolean that indicates if the operation was successful.
9 """
10 # Throws if `msg.sender` is not the minter
11 assert msg.sender == self.minter
Mostrar todo
Copiar

Sólo el minter (la cuenta que creó el contrato ERC-721) puede acuñar nuevos tókenes. Esto puede ser un problema en el futuro si queremos cambiar la identidad del minter. En un contrato de producción, es deseable una función que permita al minter transferir privilegios de minter a otra persona.

1 # Throws if `_to` is zero address
2 assert _to != ZERO_ADDRESS
3 # Add NFT. Throws if `_tokenId` is owned by someone
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True
Copiar

Por lo general, el acuñar nuevos tókenes cuenta como una transferencia desde la dirección cero.

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev Burns a specific ERC721 token.
6 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
7 address for this NFT.
8 Throws if `_tokenId` is not a valid NFT.
9 @param _tokenId uint256 id of the ERC721 token to be burned.
10 """
11 # Check requirements
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Throws if `_tokenId` is not a valid NFT
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Mostrar todo
Copiar

Cualquiera a quien se le permita transferir un token puede quemarlo. Mientras que una quema parece equivalente a transferira la dirección cero, la dirección cero no recibe el token. Esto nos permite liberar todo el almacenamiento que se utilizó para el token, lo que puede reducir el coste del gas de la transacción.

Utilizar este contrato

En contraste con 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. Así que para crear su propio contrato Vyper ERC-721, utilice este contrato(opens in a new tab) y modifíquelo para implementar la lógica de negocio que desee.

Conclusión

A modo de recapitulación, he resumido 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 tókenes todavía pueden atascarse si los envía a una dirección cuya clave privada no se conozca.
  • Cuando hay un problema con una operación, es una buena idea usar revert en la activación, en lugar de que solo aparezca un valor de fallo.
  • Las tókenes ERC-721 existen cuando tienen un propietario.
  • Existen tres maneras de ser autorizados para transferir un NFT. Puede ser el propietario, ser aprobado para un token específico, o ser un operador para todos los tókenes del propietario.
  • Los eventos pasados solo son visibles fuera de la cadena de bloques. El código ejecutándose dentro de la cadena de bloques no puede verlos.

Ahora vaya a implementar contratos seguros de Vyper.

Última edición: @bunturx(opens in a new tab), 21 de febrero de 2024

¿Le ha resultado útil este tutorial?