Pular para o conteúdo principal

Passo a passo do contrato Vyper ERC-721

vypererc-721python
Iniciante
Ori Pomerantz
1 de abril de 2021
20 minutos de leitura minute read

Introdução

O ERC-721 padrão é usado para manter a propriedade de tokens não fungíveis (NFT). ERC-20 os tokens se comportam como uma mercadoria, porque não há diferença entre os totens individuais. Em contraste com isso, ERC-721 tokens são projetados para ativos semelhantes, mas não idênticos, como diferentes cat cartoons(opens in a new tab) ou títulos de diferentes imóveis.

Neste artigo, vamos analisar o contrato ERC-721 de Ryuya Nakamura(opens in a new tab). Este contrato é escrito em Vyper(opens in a new tab), a Python-like linguagem de contrato projetada para tornar é mais difícil escrever código inseguro do que na solidez.

O 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

Comentários em Vyper, como em Python, começam com um hash (#) e continuam até o final da linha. Comentários que incluem @<keyword> são usados ​​por NatSpec(opens in a new tab) para produzir documentação legível para humanos.

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

A interface ERC-721 é construída na linguagem Vyper. você pode ver o código definido aqui(opens in a new tab). A definição da interface é escrita em Python, em vez de Vyper, porque as interfaces são usadas não apenas dentro da blockchain, mas também ao enviar à blockchain uma transação de um cliente externo, que pode ser escrito em Python.

A primeira linha importa a ‘interface’, e o segundo especifica o que estamos incrementando aqui.

A interface ERC721Receiver

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

ERC-721 suporta dois tipos de transferência:

  • transfere de, que permite ao remetente especifique qualquer endereço de destino e coloca a responsabilidade para a transferência no remetente. Isso significa que você pode transferir para um endereço inválido, caso em que o NFT será perdido para sempre.
  • safeTransferFrom, que verifica se o endereço de destino é um contrato. Se for assim, o contrato ERC-721 pergunta ao contrato receptor se deseja receber o NFT.

Para responder transferência segura de solicita um recebimento contrato deve implementar ERC721 recebedor .

1 _operator: address,
2 _from: address,
Copiar

O endereço _de é o proprietário atual do token. O endereço _operador é aquele que solicitou a transferência (esses dois podem não ser o mesmo devido às provisões).

1 _tokenId: uint256,
Copiar

ERC-721 token IDs está 256 bits. Normalmente, elas são criados por meio de uma execução de hash da descrição que o token representa.

1 _data: Bytes[1024]
Copiar

O requerimento pode ter até 1024 bytes de dados do usuário.

1 ) -> bytes32: view
Copiar

Para evitar casos em que um contrato acidentalmente aceita uma transferência o valor de retorno não é um booleano, mas 256 bits com um valor específico.

Essa função é uma view, o que significa que pode ler o estado da blockchain, mas não modificá-lo.

Eventos

Os eventos(opens in a new tab) são emitidos para informar usuários e servidores fora da blockchain de eventos. Observe que o conteúdo dos eventos não está disponível para contratos na blockchain.

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)
Exibir tudo
Copiar

Isso é similar para o evento ERC-20 Transfer, exceto que informamos um tokenId em vez de um valor. Ninguém possui o endereço zero, portanto, por convenção, o usamos para relatar a criação e a destruição de tokens.

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)
Exibir tudo
Copiar

Uma aprovação ERC-721 é semelhante a uma permissão ERC-20. Um endereço específico é autorizado a transferir um determinado símbolo. Isso fornece um mecanismo para os contratos responderem quando aceitam um token. Os contratos não podem ouvir os eventos, portanto, se você apenas transferir o token para eles, eles não “saberão” disso. Desta forma, primeiro, o proprietário envia uma aprovação e, em seguida, envia uma solicitação ao contrato: “Aprovei para você transferir o token X, faça…”.

Esse é o designe escolhido por fazer o ERC-721 padrão semelhante ao padrão ERC-20. Como os tokens ERC-721 não são fungíveis, um contrato também pode identificar que obteve um token específico olhando a propriedade do 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
Exibir tudo
Copiar

Às vezes, é útil ter um operador que pode gerenciar todos os tokens da conta de um tipo específico (aqueles gerenciados por um contrato específico), semelhante a uma procuração. Por exemplo, eu posso querer dar tal poder a um contrato que verifica se Eu não tenho contatado ele por seis meses, e se assim for distribuo os meus bens aos meus herdeiros (se um deles o pedir, contrata não pode fazer nada sem ser chamado por uma transação). No ERC-20, podemos simplesmente atribuir uma provisão alta a um contrato de herança, mas isso não funciona para ERC-721, pois os tokens não são fungíveis. Isso é o equivalente.

O valor approved nos informa se o evento é para uma aprovação ou a retirada de uma aprovação.

Variáveis ​​de Estado

Essas variáveis contêm o estado atual dos tokens: os quais estão disponíveis e a quem os possui. A maioria delas são objetos HashMap, mapeamentos unidirecionais que existem entre dois 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

As identidades de usuários e contratos no Ethereum são representados por endereços de 160 bits. Essas duas variáveis mapeiam IDs de tokens para seus proprietários e aqueles aprovados a transferi-los (no máximo um para cada). No Ethereum, os dados não inicializados são sempre zero, pois, se não houver proprietário ou transferidor aprovado, o valor desse token será zero.

1# @dev Mapping from owner address to count of his tokens.
2ownerToNFTokenCount: HashMap[address, uint256]
Copiar

Essa variável possui a contagem de tokens para cada proprietário. Não há mapeamento de proprietários para tokens, então, a única forma de identificar os tokens que um proprietário específico possui é olhar para trás no histórico de eventos da blockchain e ver os eventos Transfer apropriados. Podemos usar essa variável para saber quando temos todos os NFTs e não precisaremos mais olhar ainda mais no tempo.

Observe que esse algoritmo funciona apenas para interfaces do usuário e servidores externos. Código em execução na blockchain em si não pode ler eventos passados.

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

Uma conta pode ter mais de um único operador. Um simples HashMap é insuficiente para mantê-los, pois cada chave gera um único valor. Em vez disso, você pode usar HashMap[address, bool] como valor. Por padrão, o valor para cada endereço é False, o que significa que ele não é um operador. Você pode definir valores como True conforme necessário.

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

Novos tokens têm de ser criados de alguma forma. Neste contrato há uma única entidade que está autorizada a fazê-lo, o minter. É provável que isso seja suficiente para um jogo, por exemplo. Para outros propósitos, pode ser necessário criar uma lógica de negócio mais 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 um mecanismo para um contrato divulgar como aplicações podem se comunicar com ele, com os quais ERCs ele está em conformidade. Neste caso, o contrato está em conformidade com ERC-165 e ERC-721.

Funções

Estas são as funções que realmente implementam o ERC-721.

Construtor

1@external
2def __init__():
Copiar

No Vyper, assim como no Python, a função construtora é chamada __init__.

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

No Python e no Vyper, você também pode criar um comentário especificando uma string de múltiplas linhas (que começa e termina com """), e não usá-lo de qualquer forma. Esses comentários também podem 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 acessar variáveis de estado, você usa self.<variable name> (novamente, o mesmo que em Python).

Exibir funções

São funções que não modificam o estado da blockchain e, por isso, podem ser executadas gratuitamente se chamadas externamente. Se as funções de exibição forem chamadas por um contrato, elas ainda têm de ser executadas em cada nó e, portanto, custam gás.

1@view
2@external
Copiar

Essas palavras-chave anteriores a uma definição de função que começam com um sinal de (@) são chamadas de decoradores. Elas especificam as circunstâncias em que uma função pode ser chamada.

  • @view especifica que esta função é um modo de exibição.
  • @external especifica que essa função em particular pode ser chamada por transações e por outros contratos.
1def supportsInterface(_interfaceID: bytes32) -> bool:
Copiar

Ao contrário do Python, o Vyper é uma linguagem de tipo estática(opens in a new tab). Você não pode declarar uma variável ou um parâmetro de função, sem identificar os tipos de dados(opens in a new tab). Neste caso, o parâmetro de entrada é bytes32, um valor de 256 bits (256 bits é o tamanho da palavra nativa da Máquina Virtual do Ethereum). A saída é um booleano valor. Por convenção, os nomes dos parâmetros da função começam com um sublinhado (_).

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

Retorne o valor do self.supportedInterfaces HashMap, o qual é definido no construtor (__init__).

1### VIEW FUNCTIONS ###
Copiar

Estas são as funções de visualização que fornecem informações sobre os tokens disponíveis para usuários e outros 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
Exibir tudo
Copiar

Esta linha afirma(opens in a new tab) que _owner não é zero. Se for zero, há um erro e a operação é anulada.

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
Exibir tudo
Copiar

Na Máquina Virtual do Ethereum (EVM) qualquer armazenamento que não tenha um valor armazenado nele é zero. Se não houver token em _tokenId, o valor de self.idToOwner[_tokenId] é zero. Naquilo caso a função reverta.

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]
Exibir tudo
Copiar

Observe que getApproved pode retornar zero. Se o token for válido, ele retorna self.idToApprovals[_tokenId]. Se não houver aprovador, esse valor é zero.

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]
Exibir tudo
Copiar

Esta função checa se _operator tem permissão para gerenciar todos os tokens de _owner neste contrato. Como pode haver vários operadores, este é um Hashmap de dois níveis.

Funções Auxiliares de Transferência

Essas funções implementam operações que fazem parte da transferência ou gerenciamento de tokens.

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

Este decorador, @internal, significa que a função é somente acessível de outras funções dentro do mesmo contrato. Por convenção, estes nomes de função também começam com um sublinhado (_).

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
Exibir tudo
Copiar

Há três maneiras na qual um endereço pode ser permitido a transferir um token:

  1. O endereço é o proprietário do token
  2. O endereço é aprovado a gastar o token
  3. O endereço é um operador do proprietário do token

A função acima pode ser uma view porque ela não muda o estado. Para reduzir custos operacionais, qualquer função que possa ser uma view, deve ser uma view.

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
Exibir tudo
Copiar

Quando há um problema com uma transferência, anulamos a chamada.

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
Exibir tudo
Copiar

Altere o valor apenas se necessário. Variáveis de estado vivem no armazenamento. Escrever para o storage é uma das operações mais caras que a EVM (Máquina Virtual Ethereum) faz (em termos de gas). Portanto, é uma boa ideia minimizá-lo, mesmo escrevendo o valor existente tem um custo alto.

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 """
Exibir tudo
Copiar

Nós temos esta função interna porque há duas maneiras de transferir tokens (normal e segura), mas nós queremos somente uma única localização no código onde nós fazemos isso para facilitar auditoria.

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)
Exibir tudo
Copiar

Para emitir um evento em Vyper você usa uma declaração de log (veja aqui para mais detalhes(opens in a new tab)).

Funções de Transferência

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)
Exibir tudo
Copiar

Esta função deixa você transferir para um endereço arbitrário. A não ser que o endereço é um usuário, ou um contrato que sabe como transferir tokens, qualquer token que você transferir ficará preso no endereço e inútil.

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)
Exibir tudo
Copiar

Tudo bem fazer a transferência primeiro, porque se der um problema, vamos revertê-la de qualquer maneira, a fim de anular tudo o que foi feito durante a chamada.

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

Primeiro cheque para ver se o endereço é um contrato (se ele tem código). Se não, assuma que ele é um endereço de usuário e o usuário será capaz de usar o token ou transferi-lo. Mas não deixe que isso engane você com uma falsa sensação de segurança. Você pode perder tokens, mesmo com safeTransferFrom, se você transferi-los para um endereço que ninguém conhece a chave privada.

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

Chame o contrato-alvo para ver se ele pode receber tokens 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

Se o destino é um contrato, mas um que não aceita tokens ERC-721 (ou que decide não aceitar esta transferência em particular), reverta.

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
Exibir tudo
Copiar

Por convenção, se você não quiser ter um aprovador, designe o endereço zero, não você mesmo.

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 configurar um aprovador você pode ou ser o proprietário, ou um operador autorizado pelo proprietário.

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)
Exibir tudo
Copiar

Cunhar novos tokens e destruir os existentes

A conta que criou o contrato é o minter, o superusuário autorizado a cunhar novos NFTs. No entanto, mesmo isso não é autorizado para queimar tokens existentes. Somente o proprietário, ou uma entidade autorizada pelo proprietário, podem fazer isso.

1### MINT & BURN FUNCTIONS ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:
Copiar

Esta função sempre retorna True, porque se a operação falhar, ela é revertida.

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
Exibir tudo
Copiar

Somente o minter (a conta que criou o contrato ERC-721) pode cunhar novos tokens. Isso pode ser um problema no futuro se você quiser mudar a identidade do minter. Em um contrato de produção, provavelmente seria desejável ter uma função que permita ao minter transferir privilégios de minter para uma outra pessoa.

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 convenção, a cunhagem de novos tokens conta como uma transferência do endereço zero.

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)
Exibir tudo
Copiar

Qualquer pessoa autorizada a transferir um token, tem permissão para queimá-lo. Enquanto uma queima aparece equivalente à transferência para o endereço zero, o endereço zero não recebe de verdade o token. Isso permite-nos liberar todo o armazenamento usado pelo token, o que pode reduzir o custo de gás da transação.

Usando este contrato

Ao contrário do Solidity, o Vyper não tem herança. Esta é uma escolha de design deliberada para tornar o código mais claro e, com isso, mais fácil de proteger. Portanto, para criar seu próprio contrato Vyper ERC-721, você usa este contrato(opens in a new tab) e o modifica para implementar a lógica comercial que você desejar.

Conclusão

Recapitulando, aqui estão algumas das ideias mais importantes neste contrato:

  • Para receber os tokens ERC-721 com uma transferência segura, os contratos têm de implementar a interface ERC721Receiver.
  • Mesmo que você use a transferência segura, os tokens ainda podem ficar presos se você os enviar para um endereço cuja chave privada.
  • Quando há um problema com uma operação, é uma boa ideia fazer o revert da chamada, em vez de apenas retornar um valor de falha.
  • Os tokens ERC-721 existem quando eles têm um proprietário.
  • Existem três formas de ser autorizado a transferir um NFT. Você pode ser o proprietário, ser aprovado para um token específico, ou ser um operador para todos os tokens do proprietário.
  • Eventos passados são visíveis apenas fora da blockchain. O código executando dentro da blockchain não pode visualizá-los.

Agora, você está pronto para implementar contratos Vyper seguros.

Última edição: @rafarocha(opens in a new tab), 23 de fevereiro de 2024

Este tutorial foi útil?