Lompat ke konten utama

Panduan Kontrak ERC-721 Vyper

Vyper
erc-721
Python
Pemula
Ori Pomerantz
1 April 2021
25 menit baca

Pengantar

Standar ERC-721 digunakan untuk memegang kepemilikan Non-Fungible Token (NFT). Token ERC-20 berperilaku sebagai komoditas, karena tidak ada perbedaan antara masing-masing token. Sebaliknya, token ERC-721 dirancang untuk aset yang serupa tetapi tidak identik, seperti kartun kucing (opens in a new tab) yang berbeda atau sertifikat untuk berbagai bidang real estat.

Dalam artikel ini kita akan menganalisis kontrak ERC-721 Ryuya Nakamura (opens in a new tab). Kontrak ini ditulis dalam Vyper (opens in a new tab), bahasa kontrak mirip Python yang dirancang untuk membuatnya lebih sulit menulis kode yang tidak aman dibandingkan dengan Solidity.

Kontrak

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

Komentar di Vyper, seperti di Python, dimulai dengan tanda pagar (#) dan berlanjut hingga akhir baris. Komentar yang menyertakan @<keyword> digunakan oleh NatSpec (opens in a new tab) untuk menghasilkan dokumentasi yang dapat dibaca manusia.

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

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

Baris pertama mengimpor antarmuka, dan yang kedua menentukan bahwa kita mengimplementasikannya di sini.

Antarmuka ERC721Receiver

1# Interface for the contract called by safeTransferFrom() # Antarmuka untuk kontrak yang dipanggil oleh safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(

ERC-721 mendukung dua jenis transfer:

  • transferFrom, yang memungkinkan pengirim menentukan alamat tujuan mana pun dan menempatkan tanggung jawab transfer pada pengirim. Ini berarti Anda dapat mentransfer ke alamat yang tidak valid, yang dalam hal ini NFT akan hilang selamanya.
  • safeTransferFrom, yang memeriksa apakah alamat tujuan adalah sebuah kontrak. Jika ya, kontrak ERC-721 bertanya kepada kontrak penerima apakah ia ingin menerima NFT tersebut.

Untuk menjawab permintaan safeTransferFrom, kontrak penerima harus mengimplementasikan ERC721Receiver.

1 _operator: address,
2 _from: address,

Alamat _from adalah pemilik token saat ini. Alamat _operator adalah pihak yang meminta transfer (keduanya mungkin tidak sama, karena adanya jatah/allowance).

1 _tokenId: uint256,

ID token ERC-721 berukuran 256 bit. Biasanya ID ini dibuat dengan melakukan hash pada deskripsi dari apa pun yang diwakili oleh token tersebut.

1 _data: Bytes[1024]

Permintaan dapat memiliki hingga 1024 byte data pengguna.

1 ) -> bytes32: view

Untuk mencegah kasus di mana sebuah kontrak secara tidak sengaja menerima transfer, nilai kembaliannya bukanlah boolean, melainkan 256 bit dengan nilai tertentu.

Fungsi ini adalah view, yang berarti ia dapat membaca status blockchain, tetapi tidak dapat memodifikasinya.

Event

Event (opens in a new tab) dipancarkan untuk memberi tahu pengguna dan server di luar blockchain tentang suatu kejadian. Perhatikan bahwa konten event tidak tersedia untuk kontrak di blockchain.

1# @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are # @dev Memancarkan saat kepemilikan NFT apa pun berubah melalui mekanisme apa pun. Event ini memancarkan saat NFT
2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any # dibuat (`from` == 0) dan dihancurkan (`to` == 0). Pengecualian: selama pembuatan kontrak, sejumlah
3# number of NFTs may be created and assigned without emitting Transfer. At the time of any # NFT dapat dibuat dan ditetapkan tanpa memancarkan Transfer. Pada saat
4# transfer, the approved address for that NFT (if any) is reset to none. # transfer apa pun, alamat yang disetujui untuk NFT tersebut (jika ada) diatur ulang menjadi tidak ada.
5# @param _from Sender of NFT (if address is zero address it indicates token creation). # @param _from Pengirim NFT (jika alamat adalah alamat nol, ini menunjukkan pembuatan token).
6# @param _to Receiver of NFT (if address is zero address it indicates token destruction). # @param _to Penerima NFT (jika alamat adalah alamat nol, ini menunjukkan penghancuran token).
7# @param _tokenId The NFT that got transferred. # @param _tokenId NFT yang ditransfer.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Tampilkan semua

Ini mirip dengan event Transfer ERC-20, kecuali bahwa kita melaporkan tokenId alih-alih jumlah. Tidak ada yang memiliki alamat nol, jadi berdasarkan 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 # @dev Ini memancarkan saat alamat yang disetujui untuk NFT diubah atau ditegaskan kembali. Alamat
2# address indicates there is no approved address. When a Transfer event emits, this also # nol menunjukkan tidak ada alamat yang disetujui. Saat event Transfer memancarkan, ini juga
3# indicates that the approved address for that NFT (if any) is reset to none. # menunjukkan bahwa alamat yang disetujui untuk NFT tersebut (jika ada) diatur ulang menjadi tidak ada.
4# @param _owner Owner of NFT. # @param _owner Pemilik NFT.
5# @param _approved Address that we are approving. # @param _approved Alamat yang kami setujui.
6# @param _tokenId NFT which we are approving. # @param _tokenId NFT yang kami setujui.
7event Approval:
8 owner: indexed(address)
9 approved: indexed(address)
10 tokenId: indexed(uint256)
Tampilkan semua

Persetujuan (approval) ERC-721 mirip dengan jatah (allowance) ERC-20. Alamat tertentu diizinkan untuk mentransfer token tertentu. Ini memberikan mekanisme bagi kontrak untuk merespons ketika mereka menerima token. Kontrak tidak dapat mendengarkan event, jadi jika Anda hanya mentransfer token kepada mereka, mereka tidak "tahu" tentang hal itu. Dengan cara ini, pemilik pertama-tama mengirimkan persetujuan dan kemudian mengirimkan permintaan ke kontrak: "Saya menyetujui Anda untuk mentransfer token X, silakan lakukan ...".

Ini adalah pilihan desain untuk membuat standar ERC-721 mirip dengan standar ERC-20. Karena token ERC-721 bersifat non-fungible, sebuah kontrak juga dapat mengidentifikasi bahwa ia mendapatkan token tertentu dengan melihat kepemilikan token tersebut.

1# @dev This emits when an operator is enabled or disabled for an owner. The operator can manage # @dev Ini memancarkan saat operator diaktifkan atau dinonaktifkan untuk pemilik. Operator dapat mengelola
2# all NFTs of the owner. # semua NFT milik pemilik.
3# @param _owner Owner of NFT. # @param _owner Pemilik NFT.
4# @param _operator Address to which we are setting operator rights. # @param _operator Alamat yang kami tetapkan hak operatornya.
5# @param _approved Status of operator rights(true if operator rights are given and false if # @param _approved Status hak operator (true jika hak operator diberikan dan false jika
6# revoked). # dicabut).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Tampilkan semua

Terkadang berguna untuk memiliki operator yang dapat mengelola semua token akun dari jenis tertentu (yang dikelola oleh kontrak tertentu), mirip dengan surat kuasa. Misalnya, saya mungkin ingin memberikan kekuasaan seperti itu kepada kontrak yang memeriksa apakah saya belum menghubunginya selama enam bulan, dan jika demikian mendistribusikan aset saya kepada ahli waris saya (jika salah satu dari mereka memintanya, kontrak tidak dapat melakukan apa pun tanpa dipanggil oleh sebuah transaksi). Di ERC-20 kita bisa memberikan jatah yang tinggi ke kontrak warisan, tetapi itu tidak berlaku untuk ERC-721 karena tokennya bersifat non-fungible. Ini adalah padanannya.

Nilai approved memberi tahu kita apakah event tersebut untuk persetujuan, atau penarikan persetujuan.

Variabel Status

Variabel-variabel ini berisi status token saat ini: mana yang tersedia dan siapa pemiliknya. Sebagian besar dari ini adalah objek HashMap, pemetaan searah yang ada di antara dua tipe (opens in a new tab).

1# @dev Mapping from NFT ID to the address that owns it. # @dev Pemetaan dari ID NFT ke alamat yang memilikinya.
2idToOwner: HashMap[uint256, address]
3
4# @dev Mapping from NFT ID to approved address. # @dev Pemetaan dari ID NFT ke alamat yang disetujui.
5idToApprovals: HashMap[uint256, address]

Identitas pengguna dan kontrak di Ethereum diwakili oleh alamat 160-bit. Kedua variabel ini memetakan dari ID token ke pemiliknya dan mereka yang disetujui untuk mentransfernya (maksimal satu untuk masing-masing). Di Ethereum, data yang tidak diinisialisasi selalu nol, jadi jika tidak ada pemilik atau pentransfer yang disetujui, nilai untuk token tersebut adalah nol.

1# @dev Mapping from owner address to count of his tokens. # @dev Pemetaan dari alamat pemilik ke jumlah tokennya.
2ownerToNFTokenCount: HashMap[address, uint256]

Variabel ini menyimpan jumlah token untuk setiap pemilik. Tidak ada pemetaan dari pemilik ke token, jadi satu-satunya cara untuk mengidentifikasi token yang dimiliki oleh pemilik tertentu adalah dengan melihat kembali riwayat event blockchain dan melihat event Transfer yang sesuai. Kita dapat menggunakan variabel ini untuk mengetahui kapan kita memiliki semua NFT dan tidak perlu melihat lebih jauh ke masa lalu.

Perhatikan bahwa algoritma ini hanya berfungsi untuk antarmuka pengguna dan server eksternal. Kode yang berjalan di blockchain itu sendiri tidak dapat membaca event masa lalu.

1# @dev Mapping from owner address to mapping of operator addresses. # @dev Pemetaan dari alamat pemilik ke pemetaan alamat operator.
2ownerToOperators: HashMap[address, HashMap[address, bool]]

Sebuah akun mungkin memiliki lebih dari satu operator. HashMap sederhana tidak cukup untuk melacaknya, karena setiap kunci mengarah ke satu nilai. Sebagai gantinya, Anda dapat menggunakan HashMap[address, bool] sebagai nilainya. Secara default, nilai untuk setiap alamat adalah False, yang berarti ia bukan operator. Anda dapat mengatur nilai menjadi True sesuai kebutuhan.

1# @dev Address of minter, who can mint a token # @dev Alamat minter, yang dapat melakukan mint token
2minter: address

Token baru harus dibuat dengan suatu cara. Dalam kontrak ini ada satu entitas yang diizinkan untuk melakukannya, yaitu minter. Ini kemungkinan cukup untuk sebuah game, misalnya. Untuk tujuan lain, mungkin perlu membuat logika bisnis yang lebih rumit.

1# @dev Mapping of interface id to bool about whether or not it's supported # @dev Pemetaan id antarmuka ke bool tentang apakah itu didukung atau tidak
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ERC165 interface ID of ERC165 # @dev ID antarmuka ERC165 dari ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ERC165 interface ID of ERC721 # @dev ID antarmuka ERC165 dari ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

ERC-165 (opens in a new tab) menentukan mekanisme bagi kontrak untuk mengungkapkan bagaimana aplikasi dapat berkomunikasi dengannya, dan ERC mana yang dipatuhinya. Dalam hal ini, kontrak mematuhi ERC-165 dan ERC-721.

Fungsi

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

Konstruktor

1@external
2def __init__():

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

1 # @dev Konstruktor kontrak.
2 """
3 @dev Contract constructor.
4 """

Di Python, dan di Vyper, Anda juga dapat membuat komentar dengan menentukan string multi-baris (yang dimulai dan diakhiri dengan """), dan tidak menggunakannya dengan cara apa pun. Komentar ini juga dapat menyertakan 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

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

Fungsi View

Ini adalah fungsi-fungsi yang tidak memodifikasi status blockchain, dan oleh karena itu dapat dieksekusi secara gratis jika dipanggil secara eksternal. Jika fungsi view dipanggil oleh sebuah kontrak, fungsi tersebut tetap harus dieksekusi di setiap node dan oleh karena itu membutuhkan biaya gas.

1@view
2@external

Kata kunci sebelum definisi fungsi yang dimulai dengan tanda at (@) disebut dekorasi (decorations). Mereka menentukan keadaan di mana suatu fungsi dapat dipanggil.

  • @view menentukan bahwa fungsi ini adalah sebuah view.
  • @external menentukan bahwa fungsi khusus ini dapat dipanggil oleh transaksi dan oleh kontrak lain.
1def supportsInterface(_interfaceID: bytes32) -> bool:

Berbeda dengan Python, Vyper adalah bahasa bertipe statis (opens in a new tab). Anda tidak dapat mendeklarasikan variabel, atau parameter fungsi, tanpa mengidentifikasi tipe data (opens in a new tab). Dalam hal ini parameter inputnya adalah bytes32, nilai 256-bit (256 bit adalah ukuran kata asli dari Mesin Virtual Ethereum). Outputnya adalah nilai boolean. Berdasarkan konvensi, nama parameter fungsi dimulai dengan garis bawah (_).

1 # @dev Identifikasi antarmuka ditentukan dalam ERC-165.
2 @param _interfaceID Id dari antarmuka
3 """
4 @dev Interface identification is specified in ERC-165.
5 @param _interfaceID Id of the interface
6 """
7 return self.supportedInterfaces[_interfaceID]

Mengembalikan nilai dari HashMap self.supportedInterfaces, yang diatur dalam konstruktor (__init__).

1### VIEW FUNCTIONS ### # ## FUNGSI VIEW ###

Ini adalah fungsi view yang membuat informasi tentang token tersedia bagi pengguna dan kontrak lainnya.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 # @dev Mengembalikan jumlah NFT yang dimiliki oleh `_owner`.
5 Melempar kesalahan jika `_owner` adalah alamat nol. NFT yang ditetapkan ke alamat nol dianggap tidak valid.
6 @param _owner Alamat untuk menanyakan saldo.
7 """
8 @dev Returns the number of NFTs owned by `_owner`.
9 Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
10 @param _owner Address for whom to query the balance.
11 """
12 assert _owner != ZERO_ADDRESS
Tampilkan semua

Baris ini menegaskan (opens in a new tab) bahwa _owner bukan nol. Jika ya, terjadi kesalahan dan operasi dibatalkan (reverted).

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 # @dev Mengembalikan alamat pemilik NFT.
7 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
8 @param _tokenId Pengidentifikasi untuk NFT.
9 """
10 @dev Returns the address of the owner of the NFT.
11 Throws if `_tokenId` is not a valid NFT.
12 @param _tokenId The identifier for an NFT.
13 """
14 owner: address = self.idToOwner[_tokenId]
15 # Throws if `_tokenId` is not a valid NFT # Melempar kesalahan jika `_tokenId` bukan NFT yang valid
16 assert owner != ZERO_ADDRESS
17 return owner
Tampilkan semua

Di Mesin Virtual Ethereum (EVM), penyimpanan apa pun yang tidak memiliki nilai yang tersimpan di dalamnya adalah nol. Jika tidak ada token di _tokenId maka nilai self.idToOwner[_tokenId] adalah nol. Dalam hal ini fungsi dibatalkan.

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 # @dev Dapatkan alamat yang disetujui untuk satu NFT.
5 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
6 @param _tokenId ID NFT untuk menanyakan persetujuannya.
7 """
8 @dev Get the approved address for a single NFT.
9 Throws if `_tokenId` is not a valid NFT.
10 @param _tokenId ID of the NFT to query the approval of.
11 """
12 # Throws if `_tokenId` is not a valid NFT # Melempar kesalahan jika `_tokenId` bukan NFT yang valid
13 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
14 return self.idToApprovals[_tokenId]
Tampilkan semua

Perhatikan bahwa getApproved dapat mengembalikan nol. Jika token valid, ia mengembalikan self.idToApprovals[_tokenId]. Jika tidak ada pemberi persetujuan, nilai tersebut adalah nol.

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 # @dev Memeriksa apakah `_operator` adalah operator yang disetujui untuk `_owner`.
5 @param _owner Alamat yang memiliki NFT.
6 @param _operator Alamat yang bertindak atas nama pemilik.
7 """
8 @dev Checks if `_operator` is an approved operator for `_owner`.
9 @param _owner The address that owns the NFTs.
10 @param _operator The address that acts on behalf of the owner.
11 """
12 return (self.ownerToOperators[_owner])[_operator]
Tampilkan semua

Fungsi ini memeriksa apakah _operator diizinkan untuk mengelola semua token _owner dalam kontrak ini. Karena bisa ada beberapa operator, ini adalah HashMap dua tingkat.

Fungsi Pembantu Transfer

Fungsi-fungsi ini mengimplementasikan operasi yang merupakan bagian dari mentransfer atau mengelola token.

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

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

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 # @dev Mengembalikan apakah pembelanja yang diberikan dapat mentransfer ID token yang diberikan
3 @param spender alamat pembelanja untuk ditanyakan
4 @param tokenId uint256 ID token yang akan ditransfer
5 @return bool apakah msg.sender disetujui untuk ID token yang diberikan,
6 adalah operator dari pemilik, atau adalah pemilik token
7 """
8 @dev Returns whether the given spender can transfer a given token ID
9 @param spender address of the spender to query
10 @param tokenId uint256 ID of the token to be transferred
11 @return bool whether the msg.sender is approved for the given token ID,
12 is an operator of the owner, or is the owner of the token
13 """
14 owner: address = self.idToOwner[_tokenId]
15 spenderIsOwner: bool = owner == _spender
16 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
17 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
18 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Tampilkan semua

Ada tiga cara di mana sebuah alamat dapat diizinkan untuk mentransfer token:

  1. Alamat tersebut adalah pemilik token
  2. Alamat tersebut disetujui untuk membelanjakan token itu
  3. Alamat tersebut adalah operator untuk pemilik token

Fungsi di atas bisa menjadi view karena tidak mengubah status. Untuk mengurangi biaya operasi, fungsi apa pun yang bisa menjadi view seharusnya menjadi view.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 # @dev Tambahkan NFT ke alamat yang diberikan
4 Melempar kesalahan jika `_tokenId` dimiliki oleh seseorang.
5 """
6 @dev Add a NFT to a given address
7 Throws if `_tokenId` is owned by someone.
8 """
9 # Throws if `_tokenId` is owned by someone # Melempar kesalahan jika `_tokenId` dimiliki oleh seseorang
10 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
11 # Change the owner # Ubah pemilik
12 self.idToOwner[_tokenId] = _to
13 # Change count tracking # Ubah pelacakan jumlah
14 self.ownerToNFTokenCount[_to] += 1
15
16
17@internal
18def _removeTokenFrom(_from: address, _tokenId: uint256):
19 # @dev Hapus NFT dari alamat yang diberikan
20 Melempar kesalahan jika `_from` bukan pemilik saat ini.
21 """
22 @dev Remove a NFT from a given address
23 Throws if `_from` is not the current owner.
24 """
25 # Throws if `_from` is not the current owner # Melempar kesalahan jika `_from` bukan pemilik saat ini
26 assert self.idToOwner[_tokenId] == _from
27 # Change the owner # Ubah pemilik
28 self.idToOwner[_tokenId] = ZERO_ADDRESS
29 # Change count tracking # Ubah pelacakan jumlah
30 self.ownerToNFTokenCount[_from] -= 1
Tampilkan semua

Ketika ada masalah dengan transfer, kita membatalkan (revert) panggilan tersebut.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 # @dev Hapus persetujuan dari alamat yang diberikan
4 Melempar kesalahan jika `_owner` bukan pemilik saat ini.
5 """
6 @dev Clear an approval of a given address
7 Throws if `_owner` is not the current owner.
8 """
9 # Throws if `_owner` is not the current owner # Melempar kesalahan jika `_owner` bukan pemilik saat ini
10 assert self.idToOwner[_tokenId] == _owner
11 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
12 # Reset approvals # Atur ulang persetujuan
13 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Tampilkan semua

Hanya ubah nilai jika perlu. Variabel status berada di penyimpanan. Menulis ke penyimpanan adalah salah satu operasi paling mahal yang dilakukan EVM (Mesin Virtual Ethereum) (dalam hal gas). Oleh karena itu, ada baiknya untuk meminimalkannya, bahkan menulis nilai yang ada pun memiliki biaya yang tinggi.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 # @dev Eksekusi transfer NFT.
4 Melempar kesalahan kecuali `msg.sender` adalah pemilik saat ini, operator yang berwenang, atau alamat
5 yang disetujui untuk NFT ini. (CATATAN: `msg.sender` tidak diizinkan dalam fungsi privat jadi teruskan `_sender`.)
6 Melempar kesalahan jika `_to` adalah alamat nol.
7 Melempar kesalahan jika `_from` bukan pemilik saat ini.
8 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
9 """
10 @dev Execute transfer of a NFT.
11 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
12 address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)
13 Throws if `_to` is the zero address.
14 Throws if `_from` is not the current owner.
15 Throws if `_tokenId` is not a valid NFT.
16 """
Tampilkan semua

Kita memiliki fungsi internal ini karena ada dua cara untuk mentransfer token (biasa dan aman), tetapi kita hanya menginginkan satu lokasi dalam kode di mana kita melakukannya untuk mempermudah audit.

1 # Check requirements # Periksa persyaratan
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Throws if `_to` is the zero address # Melempar kesalahan jika `_to` adalah alamat nol
4 assert _to != ZERO_ADDRESS
5 # Clear approval. Throws if `_from` is not the current owner # Hapus persetujuan. Melempar kesalahan jika `_from` bukan pemilik saat ini
6 self._clearApproval(_from, _tokenId)
7 # Remove NFT. Throws if `_tokenId` is not a valid NFT # Hapus NFT. Melempar kesalahan jika `_tokenId` bukan NFT yang valid
8 self._removeTokenFrom(_from, _tokenId)
9 # Add NFT # Tambahkan NFT
10 self._addTokenTo(_to, _tokenId)
11 # Log the transfer # Catat transfer
12 log Transfer(_from, _to, _tokenId)
Tampilkan semua

Untuk memancarkan event di Vyper, Anda menggunakan pernyataan log (lihat di sini untuk detail lebih lanjut (opens in a new tab)).

Fungsi Transfer

1
2### TRANSFER FUNCTIONS ### # ## FUNGSI TRANSFER ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 # @dev Melempar kesalahan kecuali `msg.sender` adalah pemilik saat ini, operator yang berwenang, atau alamat
7 yang disetujui untuk NFT ini.
8 Melempar kesalahan jika `_from` bukan pemilik saat ini.
9 Melempar kesalahan jika `_to` adalah alamat nol.
10 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
11 @notice Pemanggil bertanggung jawab untuk mengonfirmasi bahwa `_to` mampu menerima NFT atau jika tidak
12 mereka mungkin hilang secara permanen.
13 @param _from Pemilik NFT saat ini.
14 @param _to Pemilik baru.
15 @param _tokenId NFT yang akan ditransfer.
16 """
17 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
18 address for this NFT.
19 Throws if `_from` is not the current owner.
20 Throws if `_to` is the zero address.
21 Throws if `_tokenId` is not a valid NFT.
22 @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
23 they maybe be permanently lost.
24 @param _from The current owner of the NFT.
25 @param _to The new owner.
26 @param _tokenId The NFT to transfer.
27 """
28 self._transferFrom(_from, _to, _tokenId, msg.sender)
Tampilkan semua

Fungsi ini memungkinkan Anda mentransfer ke alamat sembarang. Kecuali alamat tersebut adalah pengguna, atau kontrak yang tahu cara mentransfer token, token apa pun yang Anda transfer akan tersangkut di alamat tersebut dan tidak berguna.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 # @dev Mentransfer kepemilikan NFT dari satu alamat ke alamat lain.
9 Melempar kesalahan kecuali `msg.sender` adalah pemilik saat ini, operator yang berwenang, atau
10 alamat yang disetujui untuk NFT ini.
11 Melempar kesalahan jika `_from` bukan pemilik saat ini.
12 Melempar kesalahan jika `_to` adalah alamat nol.
13 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
14 Jika `_to` adalah smart contract, ia memanggil `onERC721Received` pada `_to` dan melempar kesalahan jika
15 nilai kembaliannya bukan `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
16 CATATAN: bytes4 diwakili oleh bytes32 dengan padding
17 @param _from Pemilik NFT saat ini.
18 @param _to Pemilik baru.
19 @param _tokenId NFT yang akan ditransfer.
20 @param _data Data tambahan tanpa format yang ditentukan, dikirim dalam panggilan ke `_to`.
21 """
22 @dev Transfers the ownership of an NFT from one address to another address.
23 Throws unless `msg.sender` is the current owner, an authorized operator, or the
24 approved address for this NFT.
25 Throws if `_from` is not the current owner.
26 Throws if `_to` is the zero address.
27 Throws if `_tokenId` is not a valid NFT.
28 If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
29 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
30 NOTE: bytes4 is represented by bytes32 with padding
31 @param _from The current owner of the NFT.
32 @param _to The new owner.
33 @param _tokenId The NFT to transfer.
34 @param _data Additional data with no specified format, sent in call to `_to`.
35 """
36 self._transferFrom(_from, _to, _tokenId, msg.sender)
Tampilkan semua

Tidak masalah untuk melakukan transfer terlebih dahulu karena jika ada masalah kita akan membatalkannya (revert), sehingga semua yang dilakukan dalam panggilan akan dibatalkan.

1 if _to.is_contract: # check if `_to` is a contract address # periksa apakah `_to` adalah alamat kontrak

Pertama periksa untuk melihat apakah alamat tersebut adalah sebuah kontrak (jika memiliki kode). Jika tidak, asumsikan itu adalah alamat pengguna dan pengguna akan dapat menggunakan token atau mentransfernya. Namun jangan biarkan hal itu membuat Anda merasa aman yang semu. Anda bisa kehilangan token, bahkan dengan safeTransferFrom, jika Anda mentransfernya ke alamat yang tidak ada seorang pun yang mengetahui kunci pribadinya.

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

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' # Melempar kesalahan jika tujuan transfer adalah kontrak yang tidak mengimplementasikan 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Jika tujuannya adalah sebuah kontrak, tetapi kontrak yang tidak menerima token ERC-721 (atau yang memutuskan untuk tidak menerima transfer khusus ini), batalkan (revert).

1@external
2def approve(_approved: address, _tokenId: uint256):
3 # @dev Tetapkan atau tegaskan kembali alamat yang disetujui untuk NFT. Alamat nol menunjukkan tidak ada alamat yang disetujui.
4 Melempar kesalahan kecuali `msg.sender` adalah pemilik NFT saat ini, atau operator yang berwenang dari pemilik saat ini.
5 Melempar kesalahan jika `_tokenId` bukan NFT yang valid. (CATATAN: Ini tidak ditulis di EIP)
6 Melempar kesalahan jika `_approved` adalah pemilik saat ini. (CATATAN: Ini tidak ditulis di EIP)
7 @param _approved Alamat yang akan disetujui untuk ID NFT yang diberikan.
8 @param _tokenId ID token yang akan disetujui.
9 """
10 @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
11 Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
12 Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
13 Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
14 @param _approved Address to be approved for the given NFT ID.
15 @param _tokenId ID of the token to be approved.
16 """
17 owner: address = self.idToOwner[_tokenId]
18 # Throws if `_tokenId` is not a valid NFT # Melempar kesalahan jika `_tokenId` bukan NFT yang valid
19 assert owner != ZERO_ADDRESS
20 # Throws if `_approved` is the current owner # Melempar kesalahan jika `_approved` adalah pemilik saat ini
21 assert _approved != owner
Tampilkan semua

Berdasarkan konvensi, jika Anda tidak ingin memiliki pemberi persetujuan, Anda menunjuk alamat nol, bukan diri Anda sendiri.

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

Untuk menetapkan persetujuan, Anda bisa menjadi pemilik, atau operator yang diberi wewenang oleh pemilik.

1 # Set the approval # Tetapkan persetujuan
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 # @dev Mengaktifkan atau menonaktifkan persetujuan untuk pihak ketiga ("operator") untuk mengelola semua
9 aset `msg.sender`. Ini juga memancarkan event ApprovalForAll.
10 Melempar kesalahan jika `_operator` adalah `msg.sender`. (CATATAN: Ini tidak ditulis di EIP)
11 @notice Ini berfungsi bahkan jika pengirim tidak memiliki token apa pun pada saat itu.
12 @param _operator Alamat untuk ditambahkan ke kumpulan operator yang berwenang.
13 @param _approved True jika operator disetujui, false untuk mencabut persetujuan.
14 """
15 @dev Enables or disables approval for a third party ("operator") to manage all of
16 `msg.sender`'s assets. It also emits the ApprovalForAll event.
17 Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
18 @notice This works even if sender doesn't own any tokens at the time.
19 @param _operator Address to add to the set of authorized operators.
20 @param _approved True if the operators is approved, false to revoke approval.
21 """
22 # Throws if `_operator` is the `msg.sender` # Melempar kesalahan jika `_operator` adalah `msg.sender`
23 assert _operator != msg.sender
24 self.ownerToOperators[msg.sender][_operator] = _approved
25 log ApprovalForAll(msg.sender, _operator, _approved)
Tampilkan semua

Mint Token Baru dan Hancurkan yang Sudah Ada

Akun yang membuat kontrak adalah minter, pengguna super yang berwenang untuk melakukan mint NFT baru. Namun, bahkan ia tidak diizinkan untuk membakar (burn) token yang sudah ada. Hanya pemilik, atau entitas yang diberi wewenang oleh pemilik, yang dapat melakukannya.

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

Fungsi ini selalu mengembalikan True, karena jika operasi gagal, ia akan dibatalkan (reverted).

1 # @dev Fungsi untuk melakukan mint token
2 Melempar kesalahan jika `msg.sender` bukan minter.
3 Melempar kesalahan jika `_to` adalah alamat nol.
4 Melempar kesalahan jika `_tokenId` dimiliki oleh seseorang.
5 @param _to Alamat yang akan menerima token yang di-mint.
6 @param _tokenId Id token untuk di-mint.
7 @return Boolean yang menunjukkan apakah operasi berhasil.
8 """
9 @dev Function to mint tokens
10 Throws if `msg.sender` is not the minter.
11 Throws if `_to` is zero address.
12 Throws if `_tokenId` is owned by someone.
13 @param _to The address that will receive the minted tokens.
14 @param _tokenId The token id to mint.
15 @return A boolean that indicates if the operation was successful.
16 """
17 # Throws if `msg.sender` is not the minter # Melempar kesalahan jika `msg.sender` bukan minter
18 assert msg.sender == self.minter
Tampilkan semua

Hanya minter (akun yang membuat kontrak ERC-721) yang dapat melakukan mint token baru. Ini bisa menjadi masalah di masa depan jika kita ingin mengubah identitas minter. Dalam kontrak produksi, Anda mungkin menginginkan fungsi yang memungkinkan minter untuk mentransfer hak istimewa minter kepada orang lain.

1 # Throws if `_to` is zero address # Melempar kesalahan jika `_to` adalah alamat nol
2 assert _to != ZERO_ADDRESS
3 # Add NFT. Throws if `_tokenId` is owned by someone # Tambahkan NFT. Melempar kesalahan jika `_tokenId` dimiliki oleh seseorang
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

Berdasarkan konvensi, proses mint token baru dihitung sebagai transfer dari alamat nol.

1
2@external
3def burn(_tokenId: uint256):
4 # @dev Membakar token ERC721 tertentu.
5 Melempar kesalahan kecuali `msg.sender` adalah pemilik saat ini, operator yang berwenang, atau alamat
6 yang disetujui untuk NFT ini.
7 Melempar kesalahan jika `_tokenId` bukan NFT yang valid.
8 @param _tokenId uint256 id dari token ERC721 yang akan dibakar.
9 """
10 @dev Burns a specific ERC721 token.
11 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
12 address for this NFT.
13 Throws if `_tokenId` is not a valid NFT.
14 @param _tokenId uint256 id of the ERC721 token to be burned.
15 """
16 # Check requirements # Periksa persyaratan
17 assert self._isApprovedOrOwner(msg.sender, _tokenId)
18 owner: address = self.idToOwner[_tokenId]
19 # Throws if `_tokenId` is not a valid NFT # Melempar kesalahan jika `_tokenId` bukan NFT yang valid
20 assert owner != ZERO_ADDRESS
21 self._clearApproval(owner, _tokenId)
22 self._removeTokenFrom(owner, _tokenId)
23 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Tampilkan semua

Siapa pun yang diizinkan untuk mentransfer token diizinkan untuk membakarnya (burn). Meskipun pembakaran tampak setara dengan transfer ke alamat nol, alamat nol sebenarnya tidak menerima token tersebut. Ini memungkinkan kita untuk membebaskan semua penyimpanan yang digunakan untuk token, yang dapat mengurangi biaya gas dari transaksi.

Menggunakan Kontrak Ini

Berbeda dengan Solidity, Vyper tidak memiliki pewarisan (inheritance). Ini adalah pilihan desain yang disengaja untuk membuat kode lebih jelas dan karenanya lebih mudah diamankan. Jadi untuk membuat kontrak ERC-721 Vyper Anda sendiri, Anda mengambil kontrak ini (opens in a new tab) dan memodifikasinya untuk mengimplementasikan logika bisnis yang Anda inginkan.

Kesimpulan

Sebagai ulasan, berikut adalah beberapa ide 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 bisa tersangkut jika Anda mengirimkannya ke alamat yang kunci pribadinya tidak diketahui.
  • Ketika ada masalah dengan suatu operasi, ada baiknya untuk membatalkan (revert) panggilan tersebut, daripada hanya mengembalikan nilai kegagalan.
  • Token ERC-721 ada ketika mereka memiliki pemilik.
  • Ada tiga cara untuk diberi wewenang mentransfer NFT. Anda bisa menjadi pemilik, disetujui untuk token tertentu, atau menjadi operator untuk semua token pemilik.
  • Event masa lalu hanya terlihat di luar blockchain. Kode yang berjalan di dalam blockchain tidak dapat melihatnya.

Sekarang pergilah dan implementasikan kontrak Vyper yang aman.

Lihat di sini untuk karya saya yang lain (opens in a new tab).

Pembaruan terakhir halaman: 22 Agustus 2025

Apakah tutorial ini membantu?