Vyper ERC-721 अनुबंध वॉकथ्रू
परिचय
ERC-721 मानक का उपयोग नॉन-फंजिबल टोकन (NFT) के स्वामित्व को रखने के लिए किया जाता है। ERC-20 टोकन एक कमोडिटी की तरह व्यवहार करते हैं, क्योंकि अलग-अलग टोकन के बीच कोई अंतर नहीं होता है। इसके विपरीत, ERC-721 टोकन उन संपत्तियों के लिए डिज़ाइन किए गए हैं जो समान हैं लेकिन एक जैसे नहीं हैं, जैसे कि अलग-अलग कैट कार्टून (opens in a new tab) या अचल संपत्ति के विभिन्न हिस्सों के शीर्षक।
इस लेख में हम Ryuya Nakamura के ERC-721 अनुबंध (opens in a new tab) का विश्लेषण करेंगे। यह अनुबंध Vyper (opens in a new tab) में लिखा गया है, जो एक Python जैसी अनुबंध भाषा है जिसे Solidity की तुलना में असुरक्षित कोड लिखना कठिन बनाने के लिए डिज़ाइन किया गया है।
अनुबंध
# @dev ERC-721 नॉन-फंजिबल टोकन मानक का कार्यान्वयन।
# @author Ryuya Nakamura (@nrryuya)
# यहाँ से संशोधित: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Vyper में टिप्पणियाँ (comments), Python की तरह, एक हैश (#) से शुरू होती हैं और लाइन के अंत तक जारी रहती हैं। जिन टिप्पणियों में
@<keyword> शामिल होता है, उनका उपयोग NatSpec (opens in a new tab) द्वारा मानव-पठनीय दस्तावेज़ तैयार करने के लिए किया जाता है।
from vyper.interfaces import ERC721
implements: ERC721
ERC-721 इंटरफ़ेस Vyper भाषा में अंतर्निहित (built-in) है। आप यहाँ कोड परिभाषा देख सकते हैं (opens in a new tab)। इंटरफ़ेस परिभाषा Vyper के बजाय Python में लिखी गई है, क्योंकि इंटरफेस का उपयोग न केवल ब्लॉकचेन के भीतर किया जाता है, बल्कि बाहरी क्लाइंट से ब्लॉकचेन को लेन-देन भेजते समय भी किया जाता है, जो Python में लिखा हो सकता है।
पहली लाइन इंटरफ़ेस को आयात (import) करती है, और दूसरी यह निर्दिष्ट करती है कि हम इसे यहाँ लागू कर रहे हैं।
ERC721Receiver इंटरफ़ेस
# safeTransferFrom() द्वारा कॉल किए गए अनुबंध के लिए इंटरफ़ेस
interface ERC721Receiver:
def onERC721Received(
ERC-721 दो प्रकार के ट्रांसफर का समर्थन करता है:
transferFrom, जो प्रेषक को कोई भी गंतव्य पता निर्दिष्ट करने देता है और ट्रांसफर की जिम्मेदारी प्रेषक पर डालता है। इसका मतलब है कि आप किसी अमान्य पते पर ट्रांसफर कर सकते हैं, जिस स्थिति में NFT हमेशा के लिए खो जाता है।safeTransferFrom, जो यह जांचता है कि गंतव्य पता एक अनुबंध है या नहीं। यदि ऐसा है, तो ERC-721 अनुबंध प्राप्तकर्ता अनुबंध से पूछता है कि क्या वह NFT प्राप्त करना चाहता है।
safeTransferFrom अनुरोधों का उत्तर देने के लिए प्राप्तकर्ता अनुबंध को ERC721Receiver लागू करना होता है।
_operator: address,
_from: address,
_from पता टोकन का वर्तमान स्वामी है। _operator पता वह है जिसने ट्रांसफर का अनुरोध किया था (व्यय सीमा के कारण ये दोनों समान नहीं हो सकते हैं)।
_tokenId: uint256,
ERC-721 टोकन ID 256 बिट्स के होते हैं। आमतौर पर वे टोकन जिस भी चीज़ का प्रतिनिधित्व करता है, उसके विवरण की हैशिंग करके बनाए जाते हैं।
_data: Bytes[1024]
अनुरोध में 1024 बाइट्स तक का उपयोगकर्ता डेटा हो सकता है।
) -> bytes32: view
उन मामलों को रोकने के लिए जिनमें कोई अनुबंध गलती से ट्रांसफर स्वीकार कर लेता है, रिटर्न वैल्यू बूलियन नहीं है, बल्कि एक विशिष्ट मान के साथ 256 बिट्स है।
यह फ़ंक्शन एक view है, जिसका अर्थ है कि यह ब्लॉकचेन की स्थिति को पढ़ सकता है, लेकिन इसे संशोधित नहीं कर सकता है।
घटनाएँ
ब्लॉकचेन के बाहर उपयोगकर्ताओं और सर्वरों को घटनाओं के बारे में सूचित करने के लिए घटनाएँ उत्सर्जित (emitted) की जाती हैं। ध्यान दें कि घटनाओं की सामग्री ब्लॉकचेन पर अनुबंधों के लिए उपलब्ध नहीं है।
# @dev जब किसी भी तंत्र द्वारा किसी भी NFT का स्वामित्व बदलता है तो एमिट (उत्पन्न) होता है। यह घटना तब एमिट होती है जब NFT
# बनाए जाते हैं (`from` == 0) और नष्ट किए जाते हैं (`to` == 0)। अपवाद: अनुबंध निर्माण के दौरान, कोई भी
# संख्या में NFT बनाए और असाइन किए जा सकते हैं बिना ट्रांसफर एमिट किए। किसी भी
# ट्रांसफर के समय, उस NFT के लिए स्वीकृत पता (यदि कोई हो) को शून्य (none) पर रीसेट कर दिया जाता है।
# @param _from NFT का प्रेषक (यदि पता शून्य पता है तो यह टोकन निर्माण को इंगित करता है)।
# @param _to NFT का प्राप्तकर्ता (यदि पता शून्य पता है तो यह टोकन विनाश को इंगित करता है)।
# @param _tokenId वह NFT जिसे ट्रांसफर किया गया।
event Transfer:
sender: indexed(address)
receiver: indexed(address)
tokenId: indexed(uint256)
यह ERC-20 ट्रांसफर घटना के समान है, सिवाय इसके कि हम राशि के बजाय tokenId की रिपोर्ट करते हैं। शून्य पता किसी का नहीं होता है, इसलिए परंपरा के अनुसार हम इसका उपयोग टोकन के निर्माण और विनाश की रिपोर्ट करने के लिए करते हैं।
# @dev यह तब एमिट होता है जब किसी NFT के लिए स्वीकृत पता बदला या पुनः पुष्ट किया जाता है। शून्य
# पता इंगित करता है कि कोई स्वीकृत पता नहीं है। जब कोई ट्रांसफर घटना एमिट होती है, तो यह भी
# इंगित करता है कि उस NFT के लिए स्वीकृत पता (यदि कोई हो) शून्य (none) पर रीसेट हो गया है।
# @param _owner NFT का मालिक।
# @param _approved वह पता जिसे हम स्वीकृत कर रहे हैं।
# @param _tokenId वह NFT जिसे हम स्वीकृत कर रहे हैं।
event Approval:
owner: indexed(address)
approved: indexed(address)
tokenId: indexed(uint256)
एक ERC-721 अनुमोदन (approval) ERC-20 व्यय सीमा के समान है। एक विशिष्ट पते को एक विशिष्ट टोकन ट्रांसफर करने की अनुमति है। यह अनुबंधों को टोकन स्वीकार करने पर प्रतिक्रिया देने के लिए एक तंत्र देता है। अनुबंध घटनाओं को नहीं सुन सकते हैं, इसलिए यदि आप केवल उन्हें टोकन ट्रांसफर करते हैं तो उन्हें इसके बारे में "पता" नहीं चलता है। इस तरह स्वामी पहले एक अनुमोदन प्रस्तुत करता है और फिर अनुबंध को एक अनुरोध भेजता है: "मैंने आपको टोकन X ट्रांसफर करने के लिए अनुमोदित किया है, कृपया करें ..."।
यह ERC-721 मानक को ERC-20 मानक के समान बनाने के लिए एक डिज़ाइन विकल्प है। क्योंकि ERC-721 टोकन फंजिबल नहीं हैं, एक अनुबंध टोकन के स्वामित्व को देखकर यह भी पहचान सकता है कि उसे एक विशिष्ट टोकन मिला है।
# @dev यह तब एमिट होता है जब किसी मालिक के लिए कोई ऑपरेटर सक्षम या अक्षम किया जाता है। ऑपरेटर
# मालिक के सभी NFT का प्रबंधन कर सकता है।
# @param _owner NFT का मालिक।
# @param _operator वह पता जिस पर हम ऑपरेटर अधिकार सेट कर रहे हैं।
# @param _approved ऑपरेटर अधिकारों की स्थिति (यदि ऑपरेटर अधिकार दिए गए हैं तो true और यदि
# रद्द किए गए हैं तो false)।
event ApprovalForAll:
owner: indexed(address)
operator: indexed(address)
approved: bool
कभी-कभी एक ऑपरेटर का होना उपयोगी होता है जो किसी खाते के एक विशिष्ट प्रकार के सभी टोकन (जो एक विशिष्ट अनुबंध द्वारा प्रबंधित होते हैं) का प्रबंधन कर सकता है, जो पावर ऑफ अटॉर्नी के समान है। उदाहरण के लिए, मैं एक ऐसे अनुबंध को ऐसी शक्ति देना चाह सकता हूँ जो यह जांचता है कि क्या मैंने छह महीने से उससे संपर्क नहीं किया है, और यदि ऐसा है तो मेरी संपत्ति मेरे उत्तराधिकारियों को वितरित कर दे (यदि उनमें से कोई इसके लिए पूछता है, तो अनुबंध लेन-देन द्वारा कॉल किए बिना कुछ नहीं कर सकते)। ERC-20 में हम केवल एक विरासत अनुबंध को उच्च व्यय सीमा दे सकते हैं, लेकिन यह ERC-721 के लिए काम नहीं करता है क्योंकि टोकन फंजिबल नहीं हैं। यह उसी के समतुल्य है।
approved मान हमें बताता है कि घटना अनुमोदन के लिए है, या अनुमोदन वापस लेने के लिए है।
स्थिति चर (State Variables)
इन चरों में टोकन की वर्तमान स्थिति होती है: कौन से उपलब्ध हैं और उनका स्वामी कौन है। इनमें से अधिकांश
HashMap ऑब्जेक्ट हैं, यूनिडायरेक्शनल मैपिंग जो दो प्रकारों के बीच मौजूद हैं (opens in a new tab)।
# @dev NFT ID से उस पते की मैपिंग जो इसका मालिक है।
idToOwner: HashMap[uint256, address]
# @dev NFT ID से स्वीकृत पते की मैपिंग।
idToApprovals: HashMap[uint256, address]
इथेरियम में उपयोगकर्ता और अनुबंध पहचान 160-बिट पतों द्वारा दर्शायी जाती है। ये दो चर टोकन ID से उनके स्वामियों और उन्हें ट्रांसफर करने के लिए अनुमोदित लोगों (प्रत्येक के लिए अधिकतम एक) को मैप करते हैं। इथेरियम में, अनइनिशियलाइज़्ड डेटा हमेशा शून्य होता है, इसलिए यदि कोई स्वामी या अनुमोदित ट्रांसफरकर्ता नहीं है तो उस टोकन का मान शून्य होता है।
# @dev मालिक के पते से उसके टोकन की गिनती की मैपिंग।
ownerToNFTokenCount: HashMap[address, uint256]
यह चर प्रत्येक स्वामी के लिए टोकन की गिनती रखता है। स्वामियों से टोकन तक कोई मैपिंग नहीं है, इसलिए
किसी विशिष्ट स्वामी के पास मौजूद टोकन की पहचान करने का एकमात्र तरीका ब्लॉकचेन के घटना इतिहास में वापस देखना और उपयुक्त Transfer घटनाओं को देखना है। हम इस चर का उपयोग यह जानने के लिए कर सकते हैं कि हमारे पास सभी NFT कब हैं और हमें समय में और पीछे देखने की आवश्यकता नहीं है।
ध्यान दें कि यह एल्गोरिदम केवल उपयोगकर्ता इंटरफेस और बाहरी सर्वर के लिए काम करता है। ब्लॉकचेन पर चलने वाला कोड पिछली घटनाओं को नहीं पढ़ सकता है।
# @dev मालिक के पते से ऑपरेटर पतों की मैपिंग की मैपिंग।
ownerToOperators: HashMap[address, HashMap[address, bool]]
एक खाते में एक से अधिक ऑपरेटर हो सकते हैं। उन पर नज़र रखने के लिए एक साधारण HashMap अपर्याप्त है, क्योंकि प्रत्येक कुंजी एक ही मान की ओर ले जाती है। इसके बजाय, आप
मान के रूप में HashMap[address, bool] का उपयोग कर सकते हैं। डिफ़ॉल्ट रूप से प्रत्येक पते का मान False होता है, जिसका अर्थ है कि यह एक ऑपरेटर नहीं है। आप आवश्यकतानुसार मानों को True पर सेट कर सकते हैं।
# @dev मिंटर का पता, जो टोकन मिंट कर सकता है
minter: address
नए टोकन किसी तरह बनाए जाने चाहिए। इस अनुबंध में एक ही इकाई है जिसे ऐसा करने की अनुमति है,
minter। उदाहरण के लिए, यह एक गेम के लिए पर्याप्त होने की संभावना है। अन्य उद्देश्यों के लिए, अधिक जटिल व्यावसायिक तर्क (business logic) बनाना आवश्यक हो सकता है।
# @dev इंटरफ़ेस आईडी से बूल (bool) की मैपिंग कि यह समर्थित है या नहीं
supportedInterfaces: HashMap[bytes32, bool]
# @dev ERC165 का ERC165 इंटरफ़ेस आईडी
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
# @dev ERC721 का ERC165 इंटरफ़ेस आईडी
ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
ERC-165 (opens in a new tab) एक अनुबंध के लिए यह खुलासा करने के लिए एक तंत्र निर्दिष्ट करता है कि एप्लिकेशन इसके साथ कैसे संवाद कर सकते हैं, यह किन ERC के अनुरूप है। इस मामले में, अनुबंध ERC-165 और ERC-721 के अनुरूप है।
फ़ंक्शन्स
ये वे फ़ंक्शन हैं जो वास्तव में ERC-721 को लागू करते हैं।
कंस्ट्रक्टर
@external
def __init__():
Vyper में, Python की तरह, कंस्ट्रक्टर फ़ंक्शन को __init__ कहा जाता है।
"""
@dev अनुबंध कंस्ट्रक्टर।
"""
Python में, और Vyper में, आप एक मल्टी-लाइन स्ट्रिंग (जो """ से शुरू और समाप्त होती है) निर्दिष्ट करके भी एक टिप्पणी बना सकते हैं, और इसका किसी भी तरह से उपयोग नहीं कर सकते हैं। इन टिप्पणियों में
NatSpec (opens in a new tab) भी शामिल हो सकता है।
self.supportedInterfaces[ERC165_INTERFACE_ID] = True
self.supportedInterfaces[ERC721_INTERFACE_ID] = True
self.minter = msg.sender
स्थिति चरों तक पहुँचने के लिए आप self.<variable name> का उपयोग करते हैं (फिर से, Python के समान)।
व्यू फ़ंक्शन्स
ये ऐसे फ़ंक्शन हैं जो ब्लॉकचेन की स्थिति को संशोधित नहीं करते हैं, और इसलिए यदि उन्हें बाहरी रूप से कॉल किया जाता है तो उन्हें मुफ्त में निष्पादित किया जा सकता है। यदि व्यू फ़ंक्शन्स को किसी अनुबंध द्वारा कॉल किया जाता है तो उन्हें अभी भी हर नोड पर निष्पादित किया जाना चाहिए और इसलिए गैस खर्च होती है।
@view
@external
फ़ंक्शन परिभाषा से पहले ये कीवर्ड जो एट चिह्न (@) से शुरू होते हैं, डेकोरेशन कहलाते हैं। वे
उन परिस्थितियों को निर्दिष्ट करते हैं जिनमें किसी फ़ंक्शन को कॉल किया जा सकता है।
@viewनिर्दिष्ट करता है कि यह फ़ंक्शन एक व्यू है।@externalनिर्दिष्ट करता है कि इस विशेष फ़ंक्शन को लेन-देन और अन्य अनुबंधों द्वारा कॉल किया जा सकता है।
def supportsInterface(_interfaceID: bytes32) -> bool:
Python के विपरीत, Vyper एक स्टैटिक टाइप्ड भाषा (opens in a new tab) है।
आप डेटा प्रकार (opens in a new tab) की पहचान किए बिना किसी चर, या फ़ंक्शन पैरामीटर की घोषणा नहीं कर सकते। इस मामले में इनपुट पैरामीटर bytes32 है, जो 256-बिट मान है
(256 बिट्स इथेरियम वर्चुअल मशीन का मूल शब्द आकार है)। आउटपुट एक बूलियन
मान है। परंपरा के अनुसार, फ़ंक्शन पैरामीटर के नाम अंडरस्कोर (_) से शुरू होते हैं।
"""
@dev इंटरफ़ेस पहचान ERC-165 में निर्दिष्ट है।
@param _interfaceID इंटरफ़ेस की आईडी
"""
return self.supportedInterfaces[_interfaceID]
self.supportedInterfaces HashMap से मान लौटाएँ, जो कंस्ट्रक्टर (__init__) में सेट किया गया है।
### व्यू फ़ंक्शंस ###
ये वे व्यू फ़ंक्शन हैं जो उपयोगकर्ताओं और अन्य अनुबंधों को टोकन के बारे में जानकारी उपलब्ध कराते हैं।
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev `_owner` के स्वामित्व वाले NFT की संख्या लौटाता है।
यदि `_owner` शून्य पता है तो थ्रो (throw) करता है। शून्य पता को असाइन किए गए NFT अमान्य माने जाते हैं।
@param _owner वह पता जिसके लिए बैलेंस की क्वेरी करनी है।
"""
assert _owner != ZERO_ADDRESS
यह लाइन दावा करती है (asserts) (opens in a new tab) कि _owner शून्य नहीं है। यदि ऐसा है, तो एक त्रुटि है और ऑपरेशन रिवर्ट कर दिया जाता है।
return self.ownerToNFTokenCount[_owner]
@view
@external
def ownerOf(_tokenId: uint256) -> address:
"""
@dev NFT के मालिक का पता लौटाता है।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
@param _tokenId NFT के लिए पहचानकर्ता।
"""
owner: address = self.idToOwner[_tokenId]
# यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
assert owner != ZERO_ADDRESS
return owner
इथेरियम वर्चुअल मशीन (EVM) में कोई भी स्टोरेज जिसमें कोई मान संग्रहीत नहीं है, शून्य होता है।
यदि _tokenId पर कोई टोकन नहीं है तो self.idToOwner[_tokenId] का मान शून्य है। उस
स्थिति में फ़ंक्शन रिवर्ट हो जाता है।
@view
@external
def getApproved(_tokenId: uint256) -> address:
"""
@dev एकल NFT के लिए स्वीकृत पता प्राप्त करें।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
@param _tokenId उस NFT की आईडी जिसकी स्वीकृति की क्वेरी करनी है।
"""
# यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
assert self.idToOwner[_tokenId] != ZERO_ADDRESS
return self.idToApprovals[_tokenId]
ध्यान दें कि getApproved शून्य लौटा सकता है। यदि टोकन मान्य है तो यह self.idToApprovals[_tokenId] लौटाता है।
यदि कोई अनुमोदनकर्ता नहीं है तो वह मान शून्य है।
@view
@external
def isApprovedForAll(_owner: address, _operator: address) -> bool:
"""
@dev जाँचता है कि क्या `_operator`, `_owner` के लिए एक स्वीकृत ऑपरेटर है।
@param _owner वह पता जो NFT का मालिक है।
@param _operator वह पता जो मालिक की ओर से कार्य करता है।
"""
return (self.ownerToOperators[_owner])[_operator]
यह फ़ंक्शन जांचता है कि क्या _operator को इस अनुबंध में _owner के सभी टोकन प्रबंधित करने की अनुमति है।
क्योंकि कई ऑपरेटर हो सकते हैं, यह एक दो-स्तरीय HashMap है।
ट्रांसफर हेल्पर फ़ंक्शन्स
ये फ़ंक्शन उन ऑपरेशनों को लागू करते हैं जो टोकन ट्रांसफर करने या प्रबंधित करने का हिस्सा हैं।
### ट्रांसफर फ़ंक्शन हेल्पर्स ###
@view
@internal
इस डेकोरेशन, @internal, का अर्थ है कि फ़ंक्शन केवल उसी अनुबंध के भीतर अन्य फ़ंक्शन से सुलभ है। परंपरा के अनुसार, इन फ़ंक्शन के नाम भी अंडरस्कोर (_) से शुरू होते हैं।
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
"""
@dev लौटाता है कि क्या दिया गया स्पेंडर किसी दिए गए टोकन आईडी को ट्रांसफर कर सकता है
@param spender क्वेरी करने के लिए स्पेंडर का पता
@param tokenId ट्रांसफर किए जाने वाले टोकन की uint256 आईडी
@return bool क्या msg.sender दिए गए टोकन आईडी के लिए स्वीकृत है,
मालिक का ऑपरेटर है, या टोकन का मालिक है
"""
owner: address = self.idToOwner[_tokenId]
spenderIsOwner: bool = owner == _spender
spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
तीन तरीके हैं जिनसे किसी पते को टोकन ट्रांसफर करने की अनुमति दी जा सकती है:
- पता टोकन का स्वामी है
- पते को उस टोकन को खर्च करने के लिए अनुमोदित किया गया है
- पता टोकन के स्वामी के लिए एक ऑपरेटर है
उपरोक्त फ़ंक्शन एक व्यू हो सकता है क्योंकि यह स्थिति को नहीं बदलता है। परिचालन लागत को कम करने के लिए, कोई भी फ़ंक्शन जो एक व्यू हो सकता है, उसे एक व्यू होना चाहिए।
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
"""
@dev किसी दिए गए पते पर एक NFT जोड़ें
यदि `_tokenId` का कोई मालिक है तो थ्रो करता है।
"""
# यदि `_tokenId` का कोई मालिक है तो थ्रो करता है
assert self.idToOwner[_tokenId] == ZERO_ADDRESS
# मालिक बदलें
self.idToOwner[_tokenId] = _to
# गिनती ट्रैकिंग बदलें
self.ownerToNFTokenCount[_to] += 1
@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
"""
@dev किसी दिए गए पते से एक NFT हटाएँ
यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है।
"""
# यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है
assert self.idToOwner[_tokenId] == _from
# मालिक बदलें
self.idToOwner[_tokenId] = ZERO_ADDRESS
# गिनती ट्रैकिंग बदलें
self.ownerToNFTokenCount[_from] -= 1
जब ट्रांसफर में कोई समस्या होती है तो हम कॉल को रिवर्ट कर देते हैं।
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev किसी दिए गए पते की स्वीकृति साफ़ करें
यदि `_owner` वर्तमान मालिक नहीं है तो थ्रो करता है।
"""
# यदि `_owner` वर्तमान मालिक नहीं है तो थ्रो करता है
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
# स्वीकृतियाँ रीसेट करें
self.idToApprovals[_tokenId] = ZERO_ADDRESS
केवल आवश्यक होने पर ही मान बदलें। स्थिति चर स्टोरेज में रहते हैं। स्टोरेज में लिखना EVM (इथेरियम वर्चुअल मशीन) द्वारा किए जाने वाले सबसे महंगे ऑपरेशनों में से एक है (गैस के संदर्भ में)। इसलिए, इसे कम करना एक अच्छा विचार है, यहाँ तक कि मौजूदा मान को लिखने की भी उच्च लागत होती है।
@internal
def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
"""
@dev NFT का ट्रांसफर निष्पादित करें।
थ्रो करता है जब तक कि `msg.sender` वर्तमान मालिक, एक अधिकृत ऑपरेटर, या इस NFT के लिए स्वीकृत
पता न हो। (नोट: निजी फ़ंक्शन में `msg.sender` की अनुमति नहीं है इसलिए `_sender` पास करें।)
यदि `_to` शून्य पता है तो थ्रो करता है।
यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
"""
हमारे पास यह आंतरिक फ़ंक्शन है क्योंकि टोकन ट्रांसफर करने के दो तरीके हैं (नियमित और सुरक्षित), लेकिन हम कोड में केवल एक ही स्थान चाहते हैं जहाँ हम ऑडिटिंग को आसान बनाने के लिए ऐसा करते हैं।
# आवश्यकताएँ जाँचें
assert self._isApprovedOrOwner(_sender, _tokenId)
# यदि `_to` शून्य पता है तो थ्रो करता है
assert _to != ZERO_ADDRESS
# स्वीकृति साफ़ करें। यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है
self._clearApproval(_from, _tokenId)
# NFT हटाएँ। यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
self._removeTokenFrom(_from, _tokenId)
# NFT जोड़ें
self._addTokenTo(_to, _tokenId)
# ट्रांसफर लॉग करें
log Transfer(_from, _to, _tokenId)
Vyper में किसी घटना को उत्सर्जित करने के लिए आप log कथन का उपयोग करते हैं (अधिक जानकारी के लिए यहाँ देखें (opens in a new tab))।
ट्रांसफर फ़ंक्शन्स
### ट्रांसफर फ़ंक्शंस ###
@external
def transferFrom(_from: address, _to: address, _tokenId: uint256):
"""
@dev थ्रो करता है जब तक कि `msg.sender` वर्तमान मालिक, एक अधिकृत ऑपरेटर, या इस NFT के लिए स्वीकृत
पता न हो।
यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है।
यदि `_to` शून्य पता है तो थ्रो करता है।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
@notice कॉलर यह पुष्टि करने के लिए ज़िम्मेदार है कि `_to` NFT प्राप्त करने में सक्षम है अन्यथा
वे स्थायी रूप से खो सकते हैं।
@param _from NFT का वर्तमान मालिक।
@param _to नया मालिक।
@param _tokenId ट्रांसफर करने के लिए NFT।
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
यह फ़ंक्शन आपको किसी भी मनमाने पते पर ट्रांसफर करने देता है। जब तक कि पता कोई उपयोगकर्ता न हो, या कोई ऐसा अनुबंध न हो जो टोकन ट्रांसफर करना जानता हो, आपके द्वारा ट्रांसफर किया गया कोई भी टोकन उस पते पर फंस जाएगा और बेकार हो जाएगा।
@external
def safeTransferFrom(
_from: address,
_to: address,
_tokenId: uint256,
_data: Bytes[1024]=b""
):
"""
@dev एक पते से दूसरे पते पर NFT का स्वामित्व ट्रांसफर करता है।
थ्रो करता है जब तक कि `msg.sender` वर्तमान मालिक, एक अधिकृत ऑपरेटर, या इस NFT के लिए
स्वीकृत पता न हो।
यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है।
यदि `_to` शून्य पता है तो थ्रो करता है।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
यदि `_to` एक स्मार्ट अनुबंध है, तो यह `_to` पर `onERC721Received` को कॉल करता है और यदि
रिटर्न वैल्यू `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` नहीं है तो थ्रो करता है।
नोट: bytes4 को पैडिंग के साथ bytes32 द्वारा दर्शाया गया है
@param _from NFT का वर्तमान मालिक।
@param _to नया मालिक।
@param _tokenId ट्रांसफर करने के लिए NFT।
@param _data बिना किसी निर्दिष्ट प्रारूप के अतिरिक्त डेटा, जिसे `_to` को कॉल में भेजा जाता है।
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
पहले ट्रांसफर करना ठीक है क्योंकि अगर कोई समस्या है तो हम वैसे भी रिवर्ट करने जा रहे हैं, इसलिए कॉल में किया गया सब कुछ रद्द कर दिया जाएगा।
if _to.is_contract: # जाँचें कि क्या `_to` एक अनुबंध पता है
पहले यह देखने के लिए जांचें कि क्या पता एक अनुबंध है (यदि इसमें कोड है)। यदि नहीं, तो मान लें कि यह एक उपयोगकर्ता
पता है और उपयोगकर्ता टोकन का उपयोग करने या इसे ट्रांसफर करने में सक्षम होगा। लेकिन इसे आपको सुरक्षा की झूठी भावना में न डालने दें। आप टोकन खो सकते हैं, यहाँ तक कि safeTransferFrom के साथ भी, यदि आप
उन्हें ऐसे पते पर ट्रांसफर करते हैं जिसके लिए कोई भी निजी कुंजी नहीं जानता है।
returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
यह देखने के लिए लक्ष्य अनुबंध को कॉल करें कि क्या यह ERC-721 टोकन प्राप्त कर सकता है।
# यदि ट्रांसफर गंतव्य एक अनुबंध है जो 'onERC721Received' को लागू नहीं करता है तो थ्रो करता है
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
यदि गंतव्य एक अनुबंध है, लेकिन ऐसा जो ERC-721 टोकन स्वीकार नहीं करता है (या जिसने इस विशेष ट्रांसफर को स्वीकार न करने का निर्णय लिया है), तो रिवर्ट करें।
@external
def approve(_approved: address, _tokenId: uint256):
"""
@dev किसी NFT के लिए स्वीकृत पता सेट या पुनः पुष्ट करें। शून्य पता इंगित करता है कि कोई स्वीकृत पता नहीं है।
थ्रो करता है जब तक कि `msg.sender` वर्तमान NFT मालिक, या वर्तमान मालिक का अधिकृत ऑपरेटर न हो।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है। (नोट: यह EIP में नहीं लिखा गया है)
यदि `_approved` वर्तमान मालिक है तो थ्रो करता है। (नोट: यह EIP में नहीं लिखा गया है)
@param _approved दिए गए NFT आईडी के लिए स्वीकृत किया जाने वाला पता।
@param _tokenId स्वीकृत किए जाने वाले टोकन की आईडी।
"""
owner: address = self.idToOwner[_tokenId]
# यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
assert owner != ZERO_ADDRESS
# यदि `_approved` वर्तमान मालिक है तो थ्रो करता है
assert _approved != owner
परंपरा के अनुसार यदि आप कोई अनुमोदनकर्ता नहीं चाहते हैं तो आप शून्य पता नियुक्त करते हैं, स्वयं को नहीं।
# आवश्यकताएँ जाँचें
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
अनुमोदन सेट करने के लिए आप या तो स्वामी हो सकते हैं, या स्वामी द्वारा अधिकृत ऑपरेटर हो सकते हैं।
# स्वीकृति सेट करें
self.idToApprovals[_tokenId] = _approved
log Approval(owner, _approved, _tokenId)
@external
def setApprovalForAll(_operator: address, _approved: bool):
"""
@dev किसी तीसरे पक्ष ("ऑपरेटर") को `msg.sender` की सभी संपत्तियों का प्रबंधन करने के लिए स्वीकृति सक्षम या अक्षम करता है। यह ApprovalForAll घटना भी एमिट करता है।
यदि `_operator`, `msg.sender` है तो थ्रो करता है। (नोट: यह EIP में नहीं लिखा गया है)
@notice यह तब भी काम करता है जब प्रेषक के पास उस समय कोई टोकन न हो।
@param _operator अधिकृत ऑपरेटरों के सेट में जोड़ने के लिए पता।
@param _approved यदि ऑपरेटर स्वीकृत है तो True, स्वीकृति रद्द करने के लिए false।
"""
# यदि `_operator`, `msg.sender` है तो थ्रो करता है
assert _operator != msg.sender
self.ownerToOperators[msg.sender][_operator] = _approved
log ApprovalForAll(msg.sender, _operator, _approved)
नए टोकन मिंट करें और मौजूदा टोकन नष्ट करें
जिस खाते ने अनुबंध बनाया है वह minter है, सुपर उपयोगकर्ता जो नए NFT मिंट करने के लिए अधिकृत है। हालाँकि, इसे भी मौजूदा टोकन को बर्न करने की अनुमति नहीं है। केवल स्वामी, या स्वामी द्वारा अधिकृत कोई इकाई ही ऐसा कर सकती है।
### मिंट और बर्न फ़ंक्शंस ###
@external
def mint(_to: address, _tokenId: uint256) -> bool:
यह फ़ंक्शन हमेशा True लौटाता है, क्योंकि यदि ऑपरेशन विफल हो जाता है तो इसे रिवर्ट कर दिया जाता है।
"""
@dev टोकन मिंट करने का फ़ंक्शन
यदि `msg.sender` मिंटर नहीं है तो थ्रो करता है।
यदि `_to` शून्य पता है तो थ्रो करता है।
यदि `_tokenId` का कोई मालिक है तो थ्रो करता है।
@param _to वह पता जो मिंट किए गए टोकन प्राप्त करेगा।
@param _tokenId मिंट करने के लिए टोकन आईडी।
@return एक बूलियन जो इंगित करता है कि क्या ऑपरेशन सफल रहा।
"""
# यदि `msg.sender` मिंटर नहीं है तो थ्रो करता है
assert msg.sender == self.minter
केवल मिंटर (वह खाता जिसने ERC-721 अनुबंध बनाया है) ही नए टोकन मिंट कर सकता है। भविष्य में यह एक समस्या हो सकती है यदि हम मिंटर की पहचान बदलना चाहते हैं। एक उत्पादन अनुबंध में आप शायद एक ऐसा फ़ंक्शन चाहेंगे जो मिंटर को किसी और को मिंटर विशेषाधिकार ट्रांसफर करने की अनुमति दे।
# यदि `_to` शून्य पता है तो थ्रो करता है
assert _to != ZERO_ADDRESS
# NFT जोड़ें। यदि `_tokenId` का कोई मालिक है तो थ्रो करता है
self._addTokenTo(_to, _tokenId)
log Transfer(ZERO_ADDRESS, _to, _tokenId)
return True
परंपरा के अनुसार, नए टोकन की मिंटिंग शून्य पते से ट्रांसफर के रूप में गिनी जाती है।
@external
def burn(_tokenId: uint256):
"""
@dev एक विशिष्ट ERC721 टोकन को बर्न करता है।
थ्रो करता है जब तक कि `msg.sender` वर्तमान मालिक, एक अधिकृत ऑपरेटर, या इस NFT के लिए स्वीकृत
पता न हो।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है।
@param _tokenId बर्न किए जाने वाले ERC721 टोकन की uint256 आईडी।
"""
# आवश्यकताएँ जाँचें
assert self._isApprovedOrOwner(msg.sender, _tokenId)
owner: address = self.idToOwner[_tokenId]
# यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
assert owner != ZERO_ADDRESS
self._clearApproval(owner, _tokenId)
self._removeTokenFrom(owner, _tokenId)
log Transfer(owner, ZERO_ADDRESS, _tokenId)
जिस किसी को भी टोकन ट्रांसफर करने की अनुमति है, उसे इसे बर्न करने की अनुमति है। जबकि एक बर्न शून्य पते पर ट्रांसफर के बराबर प्रतीत होता है, शून्य पता वास्तव में टोकन प्राप्त नहीं करता है। यह हमें उस सभी स्टोरेज को मुक्त करने की अनुमति देता है जिसका उपयोग टोकन के लिए किया गया था, जो लेन-देन की गैस लागत को कम कर सकता है।
इस अनुबंध का उपयोग करना
Solidity के विपरीत, Vyper में इनहेरिटेंस (inheritance) नहीं है। यह कोड को स्पष्ट और इसलिए सुरक्षित करने में आसान बनाने के लिए एक जानबूझकर किया गया डिज़ाइन विकल्प है। इसलिए अपना खुद का Vyper ERC-721 अनुबंध बनाने के लिए आप इस अनुबंध (opens in a new tab) को लेते हैं और अपने इच्छित व्यावसायिक तर्क को लागू करने के लिए इसे संशोधित करते हैं।
निष्कर्ष
समीक्षा के लिए, इस अनुबंध में कुछ सबसे महत्वपूर्ण विचार यहाँ दिए गए हैं:
- सुरक्षित ट्रांसफर के साथ ERC-721 टोकन प्राप्त करने के लिए, अनुबंधों को
ERC721Receiverइंटरफ़ेस लागू करना होगा। - भले ही आप सुरक्षित ट्रांसफर का उपयोग करते हों, फिर भी टोकन फंस सकते हैं यदि आप उन्हें ऐसे पते पर भेजते हैं जिसकी निजी कुंजी अज्ञात है।
- जब किसी ऑपरेशन में कोई समस्या होती है तो केवल विफलता मान लौटाने के बजाय कॉल को
revertकरना एक अच्छा विचार है। - ERC-721 टोकन तब मौजूद होते हैं जब उनका कोई स्वामी होता है।
- NFT ट्रांसफर करने के लिए अधिकृत होने के तीन तरीके हैं। आप स्वामी हो सकते हैं, किसी विशिष्ट टोकन के लिए अनुमोदित हो सकते हैं, या स्वामी के सभी टोकन के लिए ऑपरेटर हो सकते हैं।
- पिछली घटनाएँ केवल ब्लॉकचेन के बाहर दिखाई देती हैं। ब्लॉकचेन के अंदर चलने वाला कोड उन्हें नहीं देख सकता है।
अब जाएँ और सुरक्षित Vyper अनुबंध लागू करें।