Lanjut ke konten utama

Panduan Lengkap Kontrak ERC-721 Vyper

vypererc-721python
Pemula
Ori Pomerantz
1 April 2021
19 bacaan singkat minute read

Pendahuluan

Standar ERC-721 digunakan untuk memegang kepemilikan Token yang Tak Dapat Dipertukarkan (NFT). Token ERC-20 berperilaku sebagai komoditas, karena tidak ada perbedaan antara token individu. Sebaliknya, token ERC-721 dirancang untuk aset yang serupa tetapi tidak sama, seperti kartun kucing(opens in a new tab) atau judul untuk bagian dari real estate yang berbeda.

Dalam artikel ini kita akan menganalisa kontrak ERC-721 Ryuya Nakamura(opens in a new tab). Kontrak ini ditulis dalam Vyper(opens in a new tab), bahasa kontrak seperti Python yang dirancang untuk lebih menyulitkan penulisan kode yang tidak aman ketimbang yang ada di Solidity.

Kontrak

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
Salin

Komentar pada Vyper, seperti juga pada Python, diawali dengan tanda pagar (#) dan dilanjutkan hingga akhir baris. Komentar yang menggunakan @<keyword> digunakan oleh NatSpec(opens in a new tab) untuk memproduksi dokumentasi yang dapat dibaca oleh manusia.

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

Antarmuka ERC-721 dibangun dalam bahasa Vyper. Anda dapat melihat definisi kode tersebut di sini(opens in a new tab). Definisi antarmuka ditulis dalam Python, alih-alih Vyper, karena antarmuka tidak hanya digunakan dalam blockchain, tetapi juga saat mengirimkan transaksi ke blockchain dari klien eksternal, yang mungkin saja ditulis dalam Python.

Baris pertama mengimpor antarmuka, dan baris kedua menunjukkan bahwa kita mengimplementasikannya di sini.

Antarmuka ERC721Receiver

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

ERC-721 mendukung dua jenis transfer:

  • transferFrom, yang memungkinkan pengirim menentukan alamat tujuan dan meletakkan tanggung jawab pentransferan pada pengirimnya. Ini berarti Anda dapat mentransfer ke alamat tidak valid, yang berarti pula NFT akan hilang jika dikirim ke alamat tersebut.
  • safeTransferFrom, yang memeriksa apakah alamat tujuannya merupakan sebuah kontrak atau bukan. Jika memang demikian, maka kontrak ERC-721 akan menanyakan kontrak penerima apakah ia ingin menerima NFT atau tidak.

Untuk menjawab permintaan safeTransferFrom kontrak penerima harus mengimplementasikan ERC721Receiver.

1 _operator: address,
2 _from: address,
Salin

Alamat _from adalah pemiliki token saat ini. Alamat _operator adalah yang meminta transfer (keduanya mungkin tidak sama, karena perbedaan uang tunjangan).

1 _tokenId: uint256,
Salin

ID token ERC-721 adalah 256 bit. Biasanya ID token itu diciptakan dengan melakukan hash terhadap deskripsi mengenai apapun yang direpresentasikan token tersebut.

1 _data: Bytes[1024]
Salin

Permintaan dapat memiliki hingga 1024 bita data pengguna.

1 ) -> bytes32: view
Salin

Untuk menghindari kejadian di mana kontrak secara tidak sengaja menerima transfer, nilai pengembaliannya bukan merupakan boolean, melainkan 256 bit dengan nilai spesifik.

Fungsi ini merupakan sebuah view, yang berarti dapat membaca status blockchain tersebut, tetapi tidak dapat memodifikasinya.

Aksi

Aksi(opens in a new tab) dipancarkan untuk memberitahu aksi kepada pengguna dan server yang ada di luar blockchain. Perhatikan bahwa konten aksi tidak tersedia untuk kontrak di 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 transfered.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Tampilkan semua
Salin

Ini sama dengan aksi Transfer ERC-20, kecuali bahwa kita melaporkan tokenId ketimbang suatu jumlah. Tidak ada seorangpun yang memiliki alamat nol, sehingga secara konvensi, kita menggunakannya untuk melaporkan pembuatan dan penghancuran token.

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)
Tampilkan semua
Salin

Persetujuan ERC-721 sama dengan uang tunjangan ERC-20. Suatu alamat tertentu diizinkan untuk mentransfer token tertentu. Ini memberikan mekanisme bagi kontrak untuk merespons saat mereka menerima token. Kontrak tidak dapat mendengarkan kejadian, sehingga jika Anda hanya mentransfer token ke mereka, mereka tidak "tahu" tentang itu. Dalam cara ini, pemilik pertama mengirim persetujuan dan kemudian mengirim permintaan ke kontrak: "Saya menyetujui Anda mentransfer token X, silahkan lakukan ...".

Ini adalah pilihan rancangan untuk membuat standar ERC-721 serupa dengan standar ERC-20. Karena token ERC-721 tidak dapat dipertukarkan, suatu kontrak juga dapat mengenali bahwa ia mendapatkan token tertentu dengan melihat kepemilikan 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
Tampilkan semua
Salin

Terkadang ada gunanya memiliki operator yang dapat mengelola semua token akun dari jenis tertentu (yang dikelola oleh kontrak tertentu), sama seperti surat kuasa. Contohnya, saya mungkin ingin memberi kuasa tersebut ke kontrak yang memeriksa apakah saya belum menghubunginya selama enam bulan, dan jika demikian bagikan aset saya kepada pewaris saya (jika salah satu dari mereka memintanya, kontrak tidak dapat melakukan apa pun tanpa dipanggil oleh transaksi). Dalam ERC-20, kita hanya dapat memberi uang tunjangan besar ke kontrak warisan, tetapi itu tidak bekerja di ERC-721 karena token tidak dapat dipertukarkan. Ini bersifat setara.

Nilai approved memberi tahu kita apakah aksi merupakan persetujuan, atau penarikan dari persetujuan.

Variabel State

Variabel ini berisi state token saat ini: yang mana yang tersedia dan siapa yang memilikinya. Kebanyakan dari ini merupakan objek HashMap, pemetaan satu arah yang ada di antara dua jenis(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]
Salin

Indentitas pengguna dan kontrak di Ethereum diwakili oleh alamat 160 bit. Kedua variable tersebut dipetakan dari ID token pemilik mereka dan siapa pun yang setuju untuk mentransfernya (dengan jumlah maksimum satu untuk setiap pemilik). Dalam Ethereum, data yang tidak terinisialisasi selalu bernilai nol, jadi jika tidak ada pemilik atau pentransfer yang menyetujui, nilai token tersebut menjadi nol.

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

Variabel ini menampung hitungan token untuk setiap pemilik. Tidak ada pemetaan dari pemilik ke token, sehingga satu-satunya cara untuk mengidentifikasi token yang dimiliki pemilik tertentu adalah dengan melihat kembali di riwayat aksi blockchain-nya dan melihat aksi Transfer yang sesuai. Kita dapat menggunakan variabel ini untuk mengetahui kapan kita mendapat semua NFT tersebut dan tidak perlu memeriksanya beberapa kali sepanjang waktu.

Ingatlah bahwa algoritma ini hanya bekerja untuk antarmuka pengguna dan server eksternal. Kode yang beroperasi pada blockchain itu sendiri tidak dapat membaca aksi lampau.

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

Sebuah akun mungkin saja dapat memiliki lebih dari satu operator. Sebuah HashMap sederhana tidak cukup untuk terus melacaknya, karena setiap kunci mengarah ke sebuah nilai tunggal. Alih-alih, Anda dapat menggunakan HashMap[address, bool] sebagai nilai. Secara bawaan, nilai untuk setiap alamat adalah False, yang berarti ini bukanlah sebuah operator. Anda dapat menetapkan nilai ke True sesuai keperluan.

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

Token baru telah dibuat. Dalam kontrak ini ada entitas tunggal yang diizinkan untuk melakukannya, minter. Sebagai contoh, ini mungkin cukup untuk sebuah permainan. Untuk keperluan lainnya, membuat logika bisnis yang lebih rumit mungkin diperlukan.

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
Salin

ERC-165(opens in a new tab) menentukan mekanisme pada kontrak untuk mengungkapkan cara agar aplikasi dapat berkomunikasi dengannya, ke ERC mana yang akan ia sesuaikan. Dalam kasus ini, kontrak menyesuaikan dengan ERC-165 dan ERC-721.

Fungsi

Ini adalah fungsi-fungsi yang benar-benar mengimplementasikan ERC-721.

Konstruktor

1@external
2def __init__():
Salin

Pada Vyper, seperti di Python, fungsi konstruktor disebut __init__.

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

Di Python, dan di Vyper, Anda juga dapat membuat komentar dengan menentukan string multibaris (yang dimulai dan diakhiri dengan """), dan tidak menggunakannya sama sekali. Komentar ini juga dapat mencakup 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
Salin

Untuk mengakses variable status, Anda menggunakan self.<variable name> (sekali lagi, sama seperti di Python).

Lihat Fungsi

Berikut adalah fungsi-fungsi yang tidak mengubah state blockchain, dan karenanya dapat dieksekusikan secara bebas jika dipanggil secara eksternal. Jika fungsi tampilan dipanggil oleh sebuah kontrak, fungsi tersebut masih harus dieksekusi di setiap node dan karena itu terkena biaya gas.

1@view
2@external
Salin

Kata kunci berikut yang berhubungan dengan definisi fungsi yang dimulai dengan tanda at (@) disebut dekorasi. Tanda ini menentukan keadaan dimana sebuah fungsi dapat dipanggil.

  • @view menunjukkan bahwa fungsi ini merupakan sebuah tampilan.
  • @external menunjukkan bahwa fungsi tersebut dapat dipanggil oleh transaksi dan oleh kontrak lainnya.
1def supportsInterface(_interfaceID: bytes32) -> bool:
Salin

Berkebalikan dengan Python, Vyper adalah bahasa berjenis statis(opens in a new tab). Anda tidak dapat mendeklarasikan sebuah variabel, atau sebuah fungsi parameter, tanpa mengidentifikasi tipe datanya(opens in a new tab). Dalam kasus ini parameter inputnya adalah bytes32, sebuah nilai 256-bit (256 bit adalah ukuran kata asal dari Mesin Virtual Ethereum). Keluarannya berupa nilai boolean. Secara konvensi, nama parameter fungsinya dimulai dengan garis bawah (_).

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

Kembalikan nilai dari HashMap self.supportedInterfaces, yang ditetapkan dalam konstruktor (__init__).

1### VIEW FUNCTIONS ###
Salin

Berikut adalah fungsi-fungsi tampilan yang membuat informasi mengenai token tersedia untuk pengguna dan kontrak-kontrak lainnya.

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
Tampilkan semua
Salin

Baris ini menegaskan(opens in a new tab) bahwa _owner bukan nol. Jika benar demikian, maka ada kesalahan dan operasi akan dibalikkan.

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
Tampilkan semua
Salin

Dalam Mesin Virtual Ethereum (evm), penyimpanan mana pun yang tidak memiliki nilai yang tersimpan di dalamnya adalah nol. Jika tidak ada token pada _tokenId maka nilai dari self.idToOwner[_tokenId] adalah nol. Dalam kasus tersebut fungsinya melakukan pembalikan.

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]
Tampilkan semua
Salin

Ingatlah bahwa getApproved dapat mengembalikan nol. Jika tokennya valid, maka akan mengembalikan self.idToApprovals[_tokenId]. Jika tidak ada pemberi persetujuan maka nilainya adalah nol.

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]
Tampilkan semua
Salin

Fungsi ini memeriksa jika _operator diizinkan mengelola semua token milik _owner dalam kontrak ini. Dikarenakan dapat terjadi multioperator, ini merupakan HashMap dua tingkat.

Fungsi Transfer Pembantu

Fungsi-fungsi berikut mengimplementasikan operasi yang merupakan bagian pentransferan atau pengelolaan token.

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

Dekorasi ini, @internal, berarti bahwa fungsi hanya dapat diakses dari fungsi lainnya dalam kontrak yang sama. Secara konvensi, nama fungsi ini juga dimulai dengan garis bawah (_).

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
Tampilkan semua
Salin

Ada tiga cara di mana suatu alamat dapat dizinkan untuk mentransfer token:

  1. Alamatnya adalah pemilik token
  2. Alamat disetujui untuk menggunakan token tersebut
  3. Alamat adalah operator untuk pemilik token

Fungsi di atas dapat merupakan suatu tampilan karena ia tidak mengubah state. Untuk mengurangi biaya pengoperasian, fungsi mana pun yang dapat berfungsi sebagai tampilan seharusnya menjadi tampilan.

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
Tampilkan semua
Salin

Saat ada masalah dengan transfer, kita membalikkan pemanggilan.

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
Tampilkan semua
Salin

Hanya ubah nilainya jika diperlukan. Variabel state tinggal di penyimpanan. Menulis penyimpanan adalah salah satu operasi yang paling mahal yang dilakukan EVM (Mesin Virtual Ethereum) (jika dilhat dari penggunaan gas). Oleh karena itu, adalah ide bagus untuk meminimalkannya, bahkan menulis nilai yang sudah ada memakan biaya yang besar.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Exeute 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 """
Tampilkan semua
Salin

Kita memiliki fungsi internal ini karena ada dua cara untuk mentransfer token (reguler dan aman), tetapi kita hanya ingin melakukannya di satu lokasi dalam kode di mana kita melakukannya untuk membuat proses audit menjadi lebih mudah.

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)
Tampilkan semua
Salin

Untuk memancarkan aksi dalam Vyper, Anda menggunakan pernyataan log (lihat di sini untuk selengkapnya(opens in a new tab)).

Fungsi Transfer

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)
Tampilkan semua
Salin

Fungsi ini memungkinkan Anda mentransfer ke alamat arbitrari. Kecuali alamatnya adalah seorang pengguna, atau kontrak yang mengetahui cara mentransfer token, token mana pun yang Anda transfer akan terjebak dalam alamat tersebut dan menjadi tidak berguna.

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)
Tampilkan semua
Salin

Tidak apa-apa melakukan transfer terlebih dahulu karena lagipula jika ada masalah, kita akan membalikkannya, sehingga semua hal yang dilakukan dalam pemanggilan akan dibatalkan.

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

Pertama-tama, periksa untuk melihat apakah alamatnya adalah suatu kontrak (jika ia memiliki kode). Jika tidak, anggaplah itu adalah alamat pengguna dan penggunanya akan dapat menggunakan token atau mentransfernya. Tetapi, jangan biarkan itu membuat Anda menjadi lengah karena rasa aman yang palsu. Anda dapat kehilangan token, bahkan dengan safeTransferFrom, jika Anda mentransfer mereka ke suatu alamat yang kunci privatnya tidak diketahui siapa pun.

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

Panggil kontrak target untuk melihat apakah ia dapat menerima token 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)
Salin

Jika tujuannya adalah suatu kontrak, tetapi itu tidak menerima token ERC-721 (atau itu memutuskan untuk tidak menerima transfer tertentu ini), balikkan.

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
Tampilkan semua
Salin

Berdasarkan konvensi jika Anda tidak ingin memiliki pemberi persetujuan, maka Anda akan menugaskan ke alamat kosong, bukan ke alamat diri Anda sendiri.

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

Untuk menetapkan persetujuan, Anda dapat menjadi pemiliknya, atau operator yang diotorisasi oleh pemilik.

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)
Tampilkan semua
Salin

Cetak Token Baru dan Hancurkan Token Yang Sudah Ada

Akun yang membuat kontrak adalah minter, pengguna super yang diotorisasi untuk mencetak NFT baru. Namun, bahkan pencetak tidak diizinkan untuk membakar token yang sudah ada. Hanya pemiliklah, atau entitas yang diotorisasi oleh pemilik, yang dapat melakukannya.

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

Fungsi ini selalu mengembalikan True, karena jika operasinya gagal maka ia akan dibalikkan.

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
Tampilkan semua
Salin

Hanya pencetak (akun yang membuat kontrak ERC-721) yang dapat mencetak token baru. Ini dapat menjadi masalah di kemudian hari jika kita ingin mengubah identitas pencetak. Dalam kontrak produksi, Anda mungkin menginginkan fungsi yang memungkinkan pencetak mentransfer hak-hak istimewa pencetak kepada orang lain.

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
Salin

Berdasarkan konvensi, pencetakan token baru dihitung sebagai sebuah transfer dari alamat nol.

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)
Tampilkan semua
Salin

Siapa pun yang diizinkan untuk mentransfer sebuah token diizinkan untuk membakarnya. Sekalipun pembakaran tampak sama dengan transfer ke alamat kosong, alamat nolnya tidak benar-benar menerima token. Ini memungkinkan kita untuk membebaskan semua penyimpanan yang digunakan untuk token, yang dapat mengurangi biaya gas transaksi.

Menggunakan Kontrak ini

Berlawanan dengan Solidity, Vyper tidak memiliki warisan. Ini adalah pilihan rancangan yang disengaja untuk membuat kode lebih jelas dan karena itu lebih mudah untuk diamankan. Jadi, untuk membuat kontrak ERC-721 Vyper Anda, Anda mengambil kontrak ini(opens in a new tab) dan memodifikasinya untuk mengimplementasikan logika bisnis yang Anda inginkan.

Kesimpulan

Sebagai tinjauan, berikut adalah beberapa dari pokok pikiran terpenting dalam kontrak ini:

  • Untuk menerima token ERC-721 dengan transfer yang aman, kontrak harus mengimplementasikan antarmuka ERC721Receiver.
  • Bahkan jika Anda menggunakan transfer yang aman, token masih dapat terjebak jika Anda mengirimkannya ke alamat yang kunci privatnya tidak diketahui.
  • Saat ada masalah dengan suatu operasi, adalah ide bagus untuk revert pemanggilan, ketimbang hanya mengembalikan nilai gagal.
  • Token-token ERC-721 ada saat memiliki pemilik.
  • Ada tiga cara untuk memiliki izin mentransfer suatu NFT. Anda dapat menjadi pemilik, disetujui untuk token tertentu, atau menjadi operator untuk semua token pemilik.
  • Aksi lampau hanya terlihat dari luar blockchain. Kode yang beroperasi dalam blockchain tidak dapat melihat mereka.

Sekarang buat dan implementasikan kontrak Vyper yang aman.

Terakhir diedit: @nhsz(opens in a new tab), 15 Agustus 2023

Apakah tutorial ini membantu?