Vyper ERC-721 अनुबंध वॉकथ्रू
परिचय
ERC-721 मानक का उपयोग नॉन-फंजिबल टोकन (NFT) के स्वामित्व को रखने के लिए किया जाता है। ERC-20 टोकन एक कमोडिटी की तरह व्यवहार करते हैं, क्योंकि अलग-अलग टोकन के बीच कोई अंतर नहीं होता है। इसके विपरीत, ERC-721 टोकन उन संपत्तियों के लिए डिज़ाइन किए गए हैं जो समान हैं लेकिन एक जैसे नहीं हैं, जैसे कि अलग-अलग कैट कार्टून (एक नए टैब में खुलता है) या अचल संपत्ति के विभिन्न हिस्सों के शीर्षक।
इस लेख में हम Ryuya Nakamura के ERC-721 अनुबंध (एक नए टैब में खुलता है) का विश्लेषण करेंगे। यह अनुबंध Vyper (एक नए टैब में खुलता है) में लिखा गया है, जो एक Python जैसी अनुबंध भाषा है जिसे Solidity की तुलना में असुरक्षित कोड लिखना कठिन बनाने के लिए डिज़ाइन किया गया है।
अनुबंध
# @dev ERC-721 नॉन-फंजिबल टोकन मानक का कार्यान्वयन।
# @author Ryuya Nakamura (@nrryuya)
# यहाँ से संशोधित: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Vyper में टिप्पणियाँ (comments), Python की तरह, एक हैश (ethereum.ercs) से शुरू होती हैं और लाइन के अंत तक जारी रहती हैं। जिन टिप्पणियों में
@<keyword> शामिल होता है, उनका उपयोग NatSpec (एक नए टैब में खुलता है) द्वारा मानव-पठनीय दस्तावेज़ तैयार करने के लिए किया जाता है।
from vyper.interfaces import ERC721
implements: ERC721
ERC-721 इंटरफ़ेस Vyper भाषा में अंतर्निहित (built-in) है। आप यहाँ कोड परिभाषा देख सकते हैं (एक नए टैब में खुलता है)। इंटरफ़ेस परिभाषा Vyper के बजाय Python में लिखी गई है, क्योंकि इंटरफेस का उपयोग न केवल ब्लॉकचेन के भीतर किया जाता है, बल्कि बाहरी क्लाइंट से ब्लॉकचेन को लेन-देन भेजते समय भी किया जाता है, जो Python में लिखा हो सकता है।
पहली लाइन इंटरफ़ेस को आयात (import) करती है, और दूसरी यह निर्दिष्ट करती है कि हम इसे यहाँ लागू कर रहे हैं।
#pragma version >0.3.10
#pragma version >0.3.10
ERC721Receiver इंटरफ़ेस
# safeTransferFrom() द्वारा कॉल किए गए अनुबंध के लिए इंटरफ़ेस
interface ERC721Receiver:
def onERC721Received(
ERC-721 दो प्रकार के ट्रांसफर का समर्थन करता है:
transferFrom, जो प्रेषक को कोई भी गंतव्य पता निर्दिष्ट करने देता है और ट्रांसफर की जिम्मेदारी प्रेषक पर डालता है। इसका मतलब है कि आप किसी अमान्य पते पर ट्रांसफर कर सकते हैं, जिस स्थिति में NFT हमेशा के लिए खो जाता है।safeTransferFrom, जो यह जांचता है कि गंतव्य पता एक अनुबंध है या नहीं। यदि ऐसा है, तो ERC-721 अनुबंध प्राप्तकर्ता अनुबंध से पूछता है कि क्या वह NFT प्राप्त करना चाहता है।
safeTransferFrom अनुरोधों का उत्तर देने के लिए एक प्राप्तकर्ता अनुबंध को ERC721Receiver लागू करना होगा。
_operator: address,
_from: address,
_from पता टोकन का वर्तमान स्वामी है। _operator पता वह है जिसने ट्रांसफर का अनुरोध किया था (व्यय सीमा (allowances) के कारण ये दोनों समान नहीं हो सकते हैं)। परंपरा के अनुसार, इस अनुबंध में अधिकांश फ़ंक्शन पैरामीटर एक अंडरस्कोर (_) से शुरू होते हैं।
_tokenId: uint256,
ERC-721 टोकन आईडी 256 बिट्स के होते हैं। आमतौर पर वे टोकन जो भी दर्शाता है उसके विवरण की हैशिंग करके बनाए जाते हैं।
_data: Bytes[1024]
अनुरोध में 1024 बाइट्स तक का उपयोगकर्ता डेटा हो सकता है।
) -> bytes4: nonpayable
उन मामलों को रोकने के लिए जिनमें कोई अनुबंध गलती से ट्रांसफर स्वीकार कर लेता है, रिटर्न मान बूलियन नहीं है, बल्कि एक विशिष्ट चार-बाइट मान है, जो onERC721Received का फ़ंक्शन चयनकर्ता (selector) है। फ़ंक्शन nonpayable है क्योंकि एक प्राप्तकर्ता अनुबंध टोकन स्वीकार करते समय अपनी स्थिति बदल सकता है।
घटनाएँ
ब्लॉकचेन के बाहर उपयोगकर्ताओं और सर्वरों को घटनाओं की जानकारी देने के लिए घटनाएँ (Events) उत्सर्जित (emit) की जाती हैं। ध्यान दें कि घटनाओं की सामग्री ब्लॉकचेन पर अनुबंधों के लिए उपलब्ध नहीं है। तीन ERC-721 घटनाएँ हमारे द्वारा आयात किए गए IERC721 इंटरफ़ेस द्वारा परिभाषित की गई हैं, इसलिए यह अनुबंध उन्हें स्वयं घोषित नहीं करता है; यह उन्हें log IERC721.<Event>(...) के साथ उत्सर्जित करता है, जैसा कि हम नीचे ट्रांसफर फ़ंक्शन्स में देखेंगे।
Transfer (sender, receiver, token_id) एक NFT के स्वामित्व में बदलाव की रिपोर्ट करता है। यह ERC-20 ट्रांसफर घटना के समान है, सिवाय इसके कि हम राशि के बजाय token_id की रिपोर्ट करते हैं। शून्य पते का कोई मालिक नहीं होता है, इसलिए परंपरा के अनुसार हम इसका उपयोग टोकन के निर्माण और विनाश की रिपोर्ट करने के लिए करते हैं। इसका एक अपवाद अनुबंध निर्माण है, जिसके दौरान Transfer उत्सर्जित किए बिना किसी भी संख्या में NFT बनाए और असाइन किए जा सकते हैं।
एक ERC-721 अनुमोदन (approval) एक ERC-20 व्यय सीमा के समान है: एक विशिष्ट पते को एक विशिष्ट टोकन ट्रांसफर करने की अनुमति है, और जब भी वह स्वीकृत पता सेट या पुनः पुष्ट किया जाता है तो Approval (owner, approved, token_id) उत्सर्जित होता है। यह अनुबंधों को टोकन स्वीकार करने पर प्रतिक्रिया देने के लिए एक तंत्र देता है। अनुबंध घटनाओं को सुन नहीं सकते हैं, इसलिए यदि आप केवल उन्हें टोकन ट्रांसफर करते हैं तो उन्हें इसके बारे में "पता" नहीं चलता है। इस तरह मालिक पहले एक अनुमोदन सबमिट करता है और फिर अनुबंध को एक अनुरोध भेजता है: "मैंने आपको टोकन X ट्रांसफर करने की मंजूरी दे दी है, कृपया करें..."। यह ERC-721 मानक को ERC-20 मानक के समान बनाने के लिए एक डिज़ाइन विकल्प है। क्योंकि ERC-721 टोकन फंजिबल नहीं हैं, एक अनुबंध टोकन के स्वामित्व को देखकर यह भी पहचान सकता है कि उसे एक विशिष्ट टोकन मिला है।
अंत में, ApprovalForAll (owner, operator, approved) तब उत्सर्जित होता है जब किसी मालिक के लिए एक ऑपरेटर सक्षम या अक्षम किया जाता है। कभी-कभी एक ऐसा ऑपरेटर होना उपयोगी होता है जो किसी खाते के एक विशिष्ट प्रकार के सभी टोकन (जो एक विशिष्ट अनुबंध द्वारा प्रबंधित होते हैं) का प्रबंधन कर सके, जो पावर ऑफ अटॉर्नी के समान है। उदाहरण के लिए, मैं एक ऐसे अनुबंध को ऐसी शक्ति देना चाह सकता हूँ जो यह जांचता है कि क्या मैंने छह महीने से उससे संपर्क नहीं किया है, और यदि ऐसा है तो मेरी संपत्ति मेरे उत्तराधिकारियों को वितरित कर दे (यदि उनमें से कोई इसके लिए पूछता है, अनुबंध लेन-देन द्वारा कॉल किए बिना कुछ नहीं कर सकते)। ERC-20 में हम केवल एक विरासत अनुबंध को उच्च व्यय सीमा दे सकते हैं, लेकिन यह ERC-721 के लिए काम नहीं करता है क्योंकि टोकन फंजिबल नहीं हैं। यह उसी के समतुल्य है। approved मान हमें बताता है कि क्या घटना किसी अनुमोदन के लिए है, या किसी अनुमोदन को वापस लेने के लिए है।
स्थिति चर (State Variables)
इन चरों में टोकन की वर्तमान स्थिति होती है: कौन से उपलब्ध हैं और उनका मालिक कौन है। इनमें से अधिकांश HashMap ऑब्जेक्ट हैं, दो प्रकारों के बीच मौजूद यूनिडायरेक्शनल मैपिंग (एक नए टैब में खुलता है)।
# @dev NFT ID से उस पते पर मैपिंग जो इसका मालिक है।
idToOwner: HashMap[uint256, address]
# @dev NFT ID से स्वीकृत पते पर मैपिंग।
idToApprovals: HashMap[uint256, address]
इथेरियम में उपयोगकर्ता और अनुबंध पहचान 160-बिट पतों द्वारा दर्शाई जाती है। ये दो चर टोकन आईडी से उनके मालिकों और उन्हें ट्रांसफर करने के लिए स्वीकृत लोगों (प्रत्येक के लिए अधिकतम एक) को मैप करते हैं। इथेरियम में, अप्रारंभीकृत (uninitialized) डेटा हमेशा शून्य होता है, इसलिए यदि कोई मालिक या स्वीकृत ट्रांसफरकर्ता नहीं है तो उस टोकन का मान शून्य होता है।
# @dev मालिक के पते से उसके टोकन की गिनती तक मैपिंग।
ownerToNFTokenCount: HashMap[address, uint256]
यह चर प्रत्येक मालिक के लिए टोकन की गिनती रखता है। मालिकों से टोकन तक कोई मैपिंग नहीं है, इसलिए किसी विशिष्ट मालिक के टोकन की पहचान करने का एकमात्र तरीका ब्लॉकचेन के घटना इतिहास में वापस देखना और उपयुक्त Transfer घटनाओं को देखना है। हम इस चर का उपयोग यह जानने के लिए कर सकते हैं कि हमारे पास सभी NFT कब हैं और हमें समय में और पीछे देखने की आवश्यकता नहीं है।
ध्यान दें कि यह एल्गोरिदम केवल उपयोगकर्ता इंटरफेस और बाहरी सर्वर के लिए काम करता है। ब्लॉकचेन पर चलने वाला कोड पिछली घटनाओं को नहीं पढ़ सकता है।
# @dev मालिक के पते से ऑपरेटर पतों की मैपिंग तक मैपिंग।
ownerToOperators: HashMap[address, HashMap[address, bool]]
एक खाते में एक से अधिक ऑपरेटर हो सकते हैं। उन पर नज़र रखने के लिए एक साधारण HashMap अपर्याप्त है, क्योंकि प्रत्येक कुंजी एक ही मान की ओर ले जाती है। इसके बजाय, आप मान के रूप में HashMap[address, bool] का उपयोग कर सकते हैं। डिफ़ॉल्ट रूप से प्रत्येक पते का मान False होता है, जिसका अर्थ है कि यह एक ऑपरेटर नहीं है। आप आवश्यकतानुसार मानों को True पर सेट कर सकते हैं।
# @dev मिंटर का पता, जो टोकन मिंट कर सकता है
minter: address
नए टोकन किसी तरह बनाए जाने चाहिए। इस अनुबंध में एक ही इकाई है जिसे ऐसा करने की अनुमति है, minter। उदाहरण के लिए, एक गेम के लिए यह पर्याप्त होने की संभावना है। अन्य उद्देश्यों के लिए, अधिक जटिल व्यावसायिक तर्क बनाना आवश्यक हो सकता है।
# @dev समर्थित ERC165 इंटरफ़ेस आईडी की स्थिर सूची
SUPPORTED_INTERFACES: constant(bytes4[2]) = [
# ERC165 का ERC165 इंटरफ़ेस आईडी
0x01ffc9a7,
# ERC721 का ERC165 इंटरफ़ेस आईडी
0x80ac58cd,
]
ERC-165 (एक नए टैब में खुलता है) एक अनुबंध के लिए यह खुलासा करने के लिए एक तंत्र निर्दिष्ट करता है कि एप्लिकेशन इसके साथ कैसे संवाद कर सकते हैं, यह किन ERC के अनुरूप है। SUPPORTED_INTERFACES उन दो चार-बाइट इंटरफ़ेस आईडी की एक निरंतर सूची है जिनके अनुरूप यह अनुबंध है: स्वयं ERC-165 और ERC-721।
फ़ंक्शन्स
ये वे फ़ंक्शन हैं जो वास्तव में ERC-721 को लागू करते हैं।
कंस्ट्रक्टर
@deploy
def __init__():
Vyper में, Python की तरह, कंस्ट्रक्टर फ़ंक्शन को __init__ कहा जाता है। इसे @deploy डेकोरेशन के साथ चिह्नित किया गया है, जिसका अर्थ है कि यह एक बार चलता है, जब अनुबंध तैनात (deploy) किया जाता है।
"""
@dev अनुबंध कंस्ट्रक्टर।
"""
Python में, और Vyper में, आप एक बहु-पंक्ति स्ट्रिंग (जो """ से शुरू और समाप्त होती है) निर्दिष्ट करके भी एक टिप्पणी बना सकते हैं, और इसका किसी भी तरह से उपयोग नहीं कर सकते हैं। इन टिप्पणियों में NatSpec (एक नए टैब में खुलता है) भी शामिल हो सकता है।
self.minter = msg.sender
स्थिति चरों तक पहुँचने के लिए आप self.<variable name> का उपयोग करते हैं (फिर से, Python के समान)। कंस्ट्रक्टर उस खाते को रिकॉर्ड करता है जिसने अनुबंध को minter के रूप में तैनात किया था।
व्यू फ़ंक्शन्स (View Functions)
ये ऐसे फ़ंक्शन हैं जो ब्लॉकचेन की स्थिति को संशोधित नहीं करते हैं, और इसलिए यदि उन्हें बाहरी रूप से कॉल किया जाता है तो उन्हें मुफ्त में निष्पादित किया जा सकता है। यदि व्यू फ़ंक्शन्स को किसी अनुबंध द्वारा कॉल किया जाता है तो उन्हें अभी भी प्रत्येक नोड पर निष्पादित किया जाना चाहिए और इसलिए गैस खर्च होती है।
@view
@external
फ़ंक्शन परिभाषा से पहले ये कीवर्ड जो एट चिह्न (@) से शुरू होते हैं, डेकोरेशन कहलाते हैं। वे उन परिस्थितियों को निर्दिष्ट करते हैं जिनमें किसी फ़ंक्शन को कॉल किया जा सकता है।
@viewनिर्दिष्ट करता है कि यह फ़ंक्शन एक व्यू है।@externalनिर्दिष्ट करता है कि इस विशेष फ़ंक्शन को लेन-देन और अन्य अनुबंधों द्वारा कॉल किया जा सकता है।
def supportsInterface(interface_id: bytes4) -> bool:
Python के विपरीत, Vyper एक स्टैटिक टाइप्ड भाषा (एक नए टैब में खुलता है) है। आप डेटा प्रकार (एक नए टैब में खुलता है) की पहचान किए बिना किसी चर, या फ़ंक्शन पैरामीटर की घोषणा नहीं कर सकते। इस मामले में इनपुट पैरामीटर bytes4 है, जो चार-बाइट मान है, और आउटपुट एक बूलियन मान है।
"""
@dev इंटरफ़ेस पहचान ERC-165 में निर्दिष्ट है।
@param interface_id इंटरफ़ेस की आईडी
"""
return interface_id in SUPPORTED_INTERFACES
यदि interface_id SUPPORTED_INTERFACES सूची में इंटरफ़ेस आईडी में से एक है तो True लौटाएँ।
### व्यू फ़ंक्शन्स ###
ये व्यू फ़ंक्शन्स हैं जो उपयोगकर्ताओं और अन्य अनुबंधों को टोकन के बारे में जानकारी उपलब्ध कराते हैं।
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev `_owner` के स्वामित्व वाले NFT की संख्या लौटाता है।
यदि `_owner` शून्य पता है तो थ्रो करता है। शून्य पते को सौंपे गए NFT को अमान्य माना जाता है。
@param _owner वह पता जिसके लिए शेष राशि की क्वेरी करनी है।
"""
assert _owner != empty(address)
यह पंक्ति दावा (assert) (एक नए टैब में खुलता है) करती है कि _owner शून्य पता नहीं है, जिसे empty(address) के रूप में लिखा गया है। यदि ऐसा है, तो एक त्रुटि होती है और ऑपरेशन रिवर्ट हो जाता है।
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 != empty(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] != empty(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 यह लौटाता है कि क्या दिया गया व्ययकर्ता (spender) किसी दिए गए टोकन आईडी को ट्रांसफर कर सकता है
@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] == empty(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] = empty(address)
# गिनती ट्रैकिंग बदलें
self.ownerToNFTokenCount[_from] -= 1
जब ट्रांसफर में कोई समस्या होती है तो हम कॉल को रिवर्ट कर देते हैं।
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev किसी दिए गए पते का अनुमोदन साफ़ करें
यदि `_owner` वर्तमान मालिक नहीं है तो थ्रो करता है।
"""
# यदि `_owner` वर्तमान मालिक नहीं है तो थ्रो करता है
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != empty(address):
# अनुमोदन रीसेट करें
self.idToApprovals[_tokenId] = empty(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 != empty(address)
# अनुमोदन साफ़ करें। यदि `_from` वर्तमान मालिक नहीं है तो थ्रो करता है
self._clearApproval(_from, _tokenId)
# NFT हटाएँ। यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
self._removeTokenFrom(_from, _tokenId)
# NFT जोड़ें
self._addTokenTo(_to, _tokenId)
# ट्रांसफर लॉग करें
log IERC721.Transfer(sender=_from, receiver=_to, token_id=_tokenId)
Vyper में किसी घटना को उत्सर्जित करने के लिए आप log स्टेटमेंट का उपयोग करते हैं (अधिक जानकारी के लिए यहाँ देखें (एक नए टैब में खुलता है))। क्योंकि घटनाएँ आयातित इंटरफ़ेस से संबंधित हैं, हम उन्हें IERC721.Transfer के रूप में संदर्भित करते हैं और उनके फ़ील्ड को कीवर्ड द्वारा पास करते हैं।
### ट्रांसफर फ़ंक्शंस ###
@external
@payable
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)
यह फ़ंक्शन आपको किसी भी मनमाने पते पर ट्रांसफर करने की सुविधा देता है। जब तक कि पता कोई उपयोगकर्ता न हो, या कोई ऐसा अनुबंध न हो जो टोकन ट्रांसफर करना जानता हो, आपके द्वारा ट्रांसफर किया गया कोई भी टोकन उस पते पर फंस जाएगा और बेकार हो जाएगा।
यहाँ @payable डेकोरेशन इसलिए है क्योंकि IERC721 इंटरफ़ेस transferFrom, safeTransferFrom, और approve को payable के रूप में घोषित करता है, इसलिए जो अनुबंध इस इंटरफ़ेस को लागू करता है उसे उन हस्ताक्षरों (signatures) से मेल खाना चाहिए।
@external
@payable
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)"))` नहीं है तो थ्रो करता है।
@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: bytes4 = extcall ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
यह देखने के लिए लक्ष्य अनुबंध को कॉल करें कि क्या यह ERC-721 टोकन प्राप्त कर सकता है। Vyper 0.4 में अन्य अनुबंधों को कॉल करने के लिए उन्हें चिह्नित करने की आवश्यकता होती है, इसलिए कॉल के पहले extcall लगाया जाता है।
# यदि ट्रांसफर गंतव्य एक ऐसा अनुबंध है जो 'onERC721Received' को लागू नहीं करता है तो थ्रो करता है
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)
यदि गंतव्य एक अनुबंध है, लेकिन ऐसा जो ERC-721 टोकन स्वीकार नहीं करता है (या जिसने इस विशेष ट्रांसफर को स्वीकार न करने का निर्णय लिया है), तो रिवर्ट करें।
@external
@payable
def approve(_approved: address, _tokenId: uint256):
"""
@dev किसी NFT के लिए स्वीकृत पते को सेट या पुनः पुष्टि करें। शून्य पता इंगित करता है कि कोई स्वीकृत पता नहीं है।
थ्रो करता है जब तक कि `msg.sender` वर्तमान NFT मालिक, या वर्तमान मालिक का अधिकृत ऑपरेटर न हो।
यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है। (नोट: यह EIP में नहीं लिखा गया है)
यदि `_approved` वर्तमान मालिक है तो थ्रो करता है। (नोट: यह EIP में नहीं लिखा गया है)
@param _approved दिए गए NFT ID के लिए स्वीकृत किया जाने वाला पता।
@param _tokenId स्वीकृत किए जाने वाले टोकन की ID।
"""
owner: address = self.idToOwner[_tokenId]
# यदि `_tokenId` एक वैध NFT नहीं है तो थ्रो करता है
assert owner != empty(address)
# यदि `_approved` वर्तमान मालिक है तो थ्रो करता है
assert _approved != owner
परंपरा के अनुसार यदि आप कोई अनुमोदनकर्ता (approver) नहीं चाहते हैं तो आप शून्य पता नियुक्त करते हैं, स्वयं को नहीं।
# आवश्यकताएँ जाँचें
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
अनुमोदन सेट करने के लिए आप या तो मालिक हो सकते हैं, या मालिक द्वारा अधिकृत ऑपरेटर हो सकते हैं।
# अनुमोदन सेट करें
self.idToApprovals[_tokenId] = _approved
log IERC721.Approval(owner=owner, approved=_approved, token_id=_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 IERC721.ApprovalForAll(owner=msg.sender, operator=_operator, approved=_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 अनुबंध बनाने के लिए आप इस अनुबंध (एक नए टैब में खुलता है) को लेते हैं और अपने इच्छित व्यावसायिक तर्क को लागू करने के लिए इसे संशोधित करते हैं।
निष्कर्ष
समीक्षा के लिए, इस अनुबंध में कुछ सबसे महत्वपूर्ण विचार यहाँ दिए गए हैं:
- सुरक्षित ट्रांसफर के साथ ERC-721 टोकन प्राप्त करने के लिए, अनुबंधों को
ERC721Receiverइंटरफ़ेस लागू करना होगा। - भले ही आप सुरक्षित ट्रांसफर का उपयोग करते हों, फिर भी टोकन फंस सकते हैं यदि आप उन्हें ऐसे पते पर भेजते हैं जिसकी निजी कुंजी अज्ञात है।
- जब किसी ऑपरेशन में कोई समस्या होती है तो केवल विफलता मान लौटाने के बजाय कॉल को
revertकरना एक अच्छा विचार है। - ERC-721 टोकन तब मौजूद होते हैं जब उनका कोई स्वामी होता है।
- NFT ट्रांसफर करने के लिए अधिकृत होने के तीन तरीके हैं। आप स्वामी हो सकते हैं, किसी विशिष्ट टोकन के लिए अनुमोदित हो सकते हैं, या स्वामी के सभी टोकन के लिए ऑपरेटर हो सकते हैं।
- पिछली घटनाएँ केवल ब्लॉकचेन के बाहर दिखाई देती हैं। ब्लॉकचेन के अंदर चलने वाला कोड उन्हें नहीं देख सकता है।
अब जाएँ और सुरक्षित Vyper अनुबंध लागू करें।