मुख्य सामग्री पर जाएं

ERC-20 अनुबंध वॉक-थ्रू

Solidity
erc-20
शुरुआती
ओरी पोमेरेंट्ज़
9 मार्च 2021
32 मिनट पढ़ें

परिचय

इथेरियम के सबसे आम उपयोगों में से एक किसी समूह द्वारा एक व्यापार योग्य टोकन बनाना है, जो एक तरह से उनकी अपनी मुद्रा होती है। ये टोकन आमतौर पर एक मानक, ERC-20 का पालन करते हैं। यह मानक ऐसे टूल लिखना संभव बनाता है, जैसे तरलता पूल और वॉलेट, जो सभी ERC-20 टोकन के साथ काम करते हैं। इस लेख में हम ओपनजेपेलिन Solidity ERC20 कार्यान्वयन (opens in a new tab), और साथ ही इंटरफ़ेस परिभाषा (opens in a new tab) का विश्लेषण करेंगे।

यह एनोटेट किया गया स्रोत कोड है। यदि आप ERC-20 को लागू करना चाहते हैं, तो यह ट्यूटोरियल पढ़ें (opens in a new tab)

इंटरफ़ेस

ERC-20 जैसे मानक का उद्देश्य कई टोकन कार्यान्वयनों को अनुमति देना है जो वॉलेट और विकेंद्रीकृत एक्सचेंजों जैसे एप्लिकेशन में अंतरप्रचालनीय हों। इसे प्राप्त करने के लिए, हम एक इंटरफ़ेस (opens in a new tab) बनाते हैं। कोई भी कोड जिसे टोकन अनुबंध का उपयोग करने की आवश्यकता है वह इंटरफ़ेस में समान परिभाषाओं का उपयोग कर सकता है और उन सभी टोकन अनुबंधों के साथ संगत हो सकता है जो इसका उपयोग करते हैं, चाहे वह मेटामास्क जैसा वॉलेट हो, etherscan.io जैसा विकेंद्रीकृत एप्लिकेशन (dapp) हो, या तरलता पूल जैसा कोई अलग अनुबंध हो।

Illustration of the ERC-20 interface

यदि आप एक अनुभवी प्रोग्रामर हैं, तो आपको शायद Java (opens in a new tab) या C हेडर फ़ाइलों (opens in a new tab) में समान संरचनाएं देखना याद होगा।

यह ओपनजेपेलिन से ERC-20 इंटरफ़ेस (opens in a new tab) की परिभाषा है। यह मानव-पठनीय मानक (opens in a new tab) का Solidity कोड में अनुवाद है। बेशक, इंटरफ़ेस स्वयं यह परिभाषित नहीं करता है कि कुछ कैसे करना है। इसे नीचे अनुबंध स्रोत कोड में समझाया गया है।

 

// SPDX-License-Identifier: MIT

Solidity फ़ाइलों में एक लाइसेंस पहचानकर्ता शामिल होना चाहिए। आप यहां लाइसेंस की सूची देख सकते हैं (opens in a new tab)। यदि आपको एक अलग लाइसेंस की आवश्यकता है, तो बस इसे टिप्पणियों में समझाएं।

 

pragma solidity >=0.6.0 <0.8.0;

Solidity भाषा अभी भी तेजी से विकसित हो रही है, और नए संस्करण पुराने कोड के साथ संगत नहीं हो सकते हैं (यहां देखें (opens in a new tab))। इसलिए, न केवल भाषा का न्यूनतम संस्करण निर्दिष्ट करना एक अच्छा विचार है, बल्कि अधिकतम संस्करण भी निर्दिष्ट करना चाहिए, जो कि नवीनतम संस्करण है जिसके साथ आपने कोड का परीक्षण किया है।

 

/**
 * @dev EIP में परिभाषित ERC-20 मानक का इंटरफ़ेस।
 */

टिप्पणी में @dev NatSpec प्रारूप (opens in a new tab) का हिस्सा है, जिसका उपयोग स्रोत कोड से दस्तावेज़ तैयार करने के लिए किया जाता है।

 

interface IERC20 {

परंपरा के अनुसार, इंटरफ़ेस नाम I से शुरू होते हैं।

 

    /**
     * @dev अस्तित्व में मौजूद टोकन की मात्रा लौटाता है।
     */
    function totalSupply() external view returns (uint256);

यह फ़ंक्शन external है, जिसका अर्थ है इसे केवल अनुबंध के बाहर से कॉल किया जा सकता है (opens in a new tab)। यह अनुबंध में टोकन की कुल आपूर्ति लौटाता है। यह मान इथेरियम में सबसे आम प्रकार, अनसाइंड 256 बिट्स (256 बिट्स EVM का मूल शब्द आकार है) का उपयोग करके लौटाया जाता है। यह फ़ंक्शन एक view भी है, जिसका अर्थ है कि यह स्थिति को नहीं बदलता है, इसलिए इसे ब्लॉकचेन में हर नोड पर चलाने के बजाय एक ही नोड पर निष्पादित किया जा सकता है। इस प्रकार का फ़ंक्शन लेन-देन उत्पन्न नहीं करता है और इसमें गैस खर्च नहीं होती है।

नोट: सिद्धांत रूप में ऐसा लग सकता है कि अनुबंध का निर्माता वास्तविक मूल्य से कम कुल आपूर्ति लौटाकर धोखा दे सकता है, जिससे प्रत्येक टोकन वास्तव में जितना है उससे अधिक मूल्यवान दिखाई दे। हालाँकि, यह डर ब्लॉकचेन की वास्तविक प्रकृति को नज़रअंदाज़ करता है। ब्लॉकचेन पर होने वाली हर चीज़ को हर नोड द्वारा सत्यापित किया जा सकता है। इसे प्राप्त करने के लिए, प्रत्येक अनुबंध का मशीन भाषा कोड और स्टोरेज हर नोड पर उपलब्ध होता है। हालाँकि आपको अपने अनुबंध के लिए Solidity कोड प्रकाशित करने की आवश्यकता नहीं है, लेकिन कोई भी आपको तब तक गंभीरता से नहीं लेगा जब तक कि आप स्रोत कोड और Solidity का वह संस्करण प्रकाशित नहीं करते जिसके साथ इसे संकलित किया गया था, ताकि आपके द्वारा प्रदान किए गए मशीन भाषा कोड के विरुद्ध इसे सत्यापित किया जा सके। उदाहरण के लिए, यह अनुबंध (opens in a new tab) देखें।

 

    /**
     * @dev `account` के स्वामित्व वाले टोकन की मात्रा लौटाता है।
     */
    function balanceOf(address account) external view returns (uint256);

जैसा कि नाम से पता चलता है, balanceOf किसी खाते का बैलेंस लौटाता है। इथेरियम खातों को Solidity में address प्रकार का उपयोग करके पहचाना जाता है, जो 160 बिट्स रखता है। यह external और view भी है।

 

    /**
     * @dev कॉलर के खाते से `recipient` को `amount` टोकन ट्रांसफर करता है।
     *
     * एक बूलियन मान लौटाता है जो यह दर्शाता है कि ऑपरेशन सफल हुआ या नहीं।
     *
     * एक {Transfer} घटना उत्सर्जित करता है।
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

transfer फ़ंक्शन कॉलर से एक अलग पते पर टोकन ट्रांसफर करता है। इसमें स्थिति में बदलाव शामिल है, इसलिए यह view नहीं है। जब कोई उपयोगकर्ता इस फ़ंक्शन को कॉल करता है तो यह एक लेन-देन बनाता है और इसमें गैस खर्च होती है। यह ब्लॉकचेन पर सभी को घटना की जानकारी देने के लिए एक घटना, Transfer, भी उत्सर्जित करता है।

फ़ंक्शन में दो अलग-अलग प्रकार के कॉलर्स के लिए दो प्रकार के आउटपुट होते हैं:

  • उपयोगकर्ता जो सीधे उपयोगकर्ता इंटरफ़ेस से फ़ंक्शन को कॉल करते हैं। आमतौर पर उपयोगकर्ता एक लेन-देन सबमिट करता है और प्रतिक्रिया की प्रतीक्षा नहीं करता है, जिसमें अनिश्चित समय लग सकता है। उपयोगकर्ता लेन-देन रसीद (जिसे लेनदेन हैश द्वारा पहचाना जाता है) या Transfer घटना को देखकर पता लगा सकता है कि क्या हुआ।
  • अन्य अनुबंध, जो समग्र लेन-देन के हिस्से के रूप में फ़ंक्शन को कॉल करते हैं। उन अनुबंधों को तुरंत परिणाम मिलता है, क्योंकि वे उसी लेन-देन में चलते हैं, इसलिए वे फ़ंक्शन रिटर्न मान का उपयोग कर सकते हैं।

अनुबंध की स्थिति को बदलने वाले अन्य फ़ंक्शंस द्वारा भी इसी प्रकार का आउटपुट बनाया जाता है।

 

व्यय सीमाएं किसी खाते को कुछ टोकन खर्च करने की अनुमति देती हैं जो किसी भिन्न स्वामी के होते हैं। यह उपयोगी है, उदाहरण के लिए, उन अनुबंधों के लिए जो विक्रेता के रूप में कार्य करते हैं। अनुबंध घटनाओं की निगरानी नहीं कर सकते हैं, इसलिए यदि कोई खरीदार सीधे विक्रेता अनुबंध में टोकन ट्रांसफर करता है तो उस अनुबंध को पता नहीं चलेगा कि उसे भुगतान किया गया था। इसके बजाय, खरीदार विक्रेता अनुबंध को एक निश्चित राशि खर्च करने की अनुमति देता है, और विक्रेता उस राशि को ट्रांसफर करता है। यह एक फ़ंक्शन के माध्यम से किया जाता है जिसे विक्रेता अनुबंध कॉल करता है, ताकि विक्रेता अनुबंध जान सके कि यह सफल रहा या नहीं।

    /**
     * @dev उन शेष टोकन की संख्या लौटाता है जिन्हें `spender` को {transferFrom} के माध्यम से `owner` की ओर से खर्च करने की अनुमति होगी। यह डिफ़ॉल्ट रूप से शून्य होता है।
     *
     * यह मान तब बदलता है जब {approve} या {transferFrom} को कॉल किया जाता है।
     */
    function allowance(address owner, address spender) external view returns (uint256);

allowance फ़ंक्शन किसी को भी यह देखने के लिए क्वेरी करने देता है कि एक पता (owner) दूसरे पते (spender) को कितनी व्यय सीमा खर्च करने देता है।

 

approve फ़ंक्शन एक व्यय सीमा बनाता है। यह संदेश अवश्य पढ़ें कि इसका दुरुपयोग कैसे किया जा सकता है। इथेरियम में आप अपने स्वयं के लेन-देन के क्रम को नियंत्रित करते हैं, लेकिन आप उस क्रम को नियंत्रित नहीं कर सकते जिसमें अन्य लोगों के लेन-देन निष्पादित किए जाएंगे, जब तक कि आप अपना लेन-देन तब तक सबमिट नहीं करते जब तक कि आप यह न देख लें कि दूसरे पक्ष का लेन-देन हो गया है।

 

    /**
     * @dev व्यय सीमा तंत्र का उपयोग करके `sender` से `recipient` को `amount` टोकन ट्रांसफर करता है। फिर कॉलर की व्यय सीमा से `amount` काट लिया जाता है।
     *
     * एक बूलियन मान लौटाता है जो यह दर्शाता है कि ऑपरेशन सफल हुआ या नहीं।
     *
     * एक {Transfer} घटना उत्सर्जित करता है।
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

अंत में, transferFrom का उपयोग खर्च करने वाले द्वारा वास्तव में व्यय सीमा खर्च करने के लिए किया जाता है।

 

ये घटनाएँ तब उत्सर्जित होती हैं जब ERC-20 अनुबंध की स्थिति बदलती है।

वास्तविक अनुबंध

यह वास्तविक अनुबंध है जो ERC-20 मानक को लागू करता है, यहां से लिया गया है (opens in a new tab)। इसका उपयोग ज्यों का त्यों करने के लिए नहीं किया गया है, लेकिन आप इसे किसी उपयोगी चीज़ में विस्तारित करने के लिए इससे विरासत (inherit) (opens in a new tab) ले सकते हैं।

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

 

आयात कथन (Import Statements)

ऊपर दी गई इंटरफ़ेस परिभाषाओं के अलावा, अनुबंध परिभाषा दो अन्य फ़ाइलों को आयात करती है:


import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
  • GSN/Context.sol OpenGSN (opens in a new tab) का उपयोग करने के लिए आवश्यक परिभाषाएँ हैं, एक ऐसी प्रणाली जो बिना ईथर वाले उपयोगकर्ताओं को ब्लॉकचेन का उपयोग करने की अनुमति देती है। ध्यान दें कि यह एक पुराना संस्करण है, यदि आप OpenGSN के साथ एकीकृत करना चाहते हैं तो इस ट्यूटोरियल का उपयोग करें (opens in a new tab)
  • SafeMath लाइब्रेरी (opens in a new tab), जो Solidity संस्करणों <0.8.0 के लिए अंकगणितीय ओवरफ़्लो/अंडरफ़्लो को रोकती है। Solidity ≥0.8.0 में, अंकगणितीय संचालन स्वचालित रूप से ओवरफ़्लो/अंडरफ़्लो पर रिवर्ट हो जाते हैं, जिससे SafeMath अनावश्यक हो जाता है। यह अनुबंध पुराने कंपाइलर संस्करणों के साथ बैकवर्ड संगतता के लिए SafeMath का उपयोग करता है।

 

यह टिप्पणी अनुबंध के उद्देश्य को समझाती है।

अनुबंध परिभाषा

contract ERC20 is Context, IERC20 {

यह पंक्ति विरासत को निर्दिष्ट करती है, इस मामले में ऊपर से IERC20 और OpenGSN के लिए Context से।

 


    using SafeMath for uint256;

यह पंक्ति SafeMath लाइब्रेरी को uint256 प्रकार से जोड़ती है। आप इस लाइब्रेरी को यहां (opens in a new tab) पा सकते हैं।

चर परिभाषाएँ (Variable Definitions)

ये परिभाषाएँ अनुबंध के स्थिति चरों को निर्दिष्ट करती हैं। इन चरों को private घोषित किया गया है, लेकिन इसका मतलब केवल यह है कि ब्लॉकचेन पर अन्य अनुबंध उन्हें पढ़ नहीं सकते हैं। ब्लॉकचेन पर कोई रहस्य नहीं हैं, हर नोड पर सॉफ़्टवेयर में हर ब्लॉक पर हर अनुबंध की स्थिति होती है। परंपरा के अनुसार, स्थिति चरों का नाम _<something> रखा जाता है।

पहले दो चर मैपिंग (opens in a new tab) हैं, जिसका अर्थ है कि वे लगभग एसोसिएटिव एरे (opens in a new tab) के समान व्यवहार करते हैं, सिवाय इसके कि कुंजियाँ संख्यात्मक मान हैं। स्टोरेज केवल उन प्रविष्टियों के लिए आवंटित किया जाता है जिनके मान डिफ़ॉल्ट (शून्य) से भिन्न होते हैं।

    mapping (address => uint256) private _balances;

पहली मैपिंग, _balances, पते और इस टोकन के उनके संबंधित बैलेंस हैं। बैलेंस तक पहुंचने के लिए, इस सिंटैक्स का उपयोग करें: _balances[<address>]

 

    mapping (address => mapping (address => uint256)) private _allowances;

यह चर, _allowances, पहले बताई गई व्यय सीमाओं को संग्रहीत करता है। पहला सूचकांक टोकन का स्वामी है, और दूसरा व्यय सीमा वाला अनुबंध है। पता A पता B के खाते से कितनी राशि खर्च कर सकता है, यह जानने के लिए _allowances[B][A] का उपयोग करें।

 

    uint256 private _totalSupply;

जैसा कि नाम से पता चलता है, यह चर टोकन की कुल आपूर्ति का ट्रैक रखता है।

 

    string private _name;
    string private _symbol;
    uint8 private _decimals;

इन तीन चरों का उपयोग पठनीयता में सुधार के लिए किया जाता है। पहले दो स्व-व्याख्यात्मक हैं, लेकिन _decimals नहीं है।

एक ओर, इथेरियम में फ़्लोटिंग पॉइंट या भिन्नात्मक चर नहीं होते हैं। दूसरी ओर, मनुष्य टोकन को विभाजित करने में सक्षम होना पसंद करते हैं। लोगों द्वारा मुद्रा के लिए सोने को चुनने का एक कारण यह था कि जब कोई बतख के मूल्य के बराबर गाय खरीदना चाहता था तो छुट्टे देना मुश्किल था।

समाधान यह है कि पूर्णांकों का ट्रैक रखा जाए, लेकिन वास्तविक टोकन के बजाय एक भिन्नात्मक टोकन गिना जाए जो लगभग बेकार है। ईथर के मामले में, भिन्नात्मक टोकन को Wei कहा जाता है, और 10^18 Wei एक ETH के बराबर है। लिखते समय, 10,000,000,000,000 Wei लगभग एक अमेरिकी या यूरो सेंट है।

एप्लिकेशन को यह जानने की आवश्यकता है कि टोकन बैलेंस कैसे प्रदर्शित किया जाए। यदि किसी उपयोगकर्ता के पास 3,141,000,000,000,000,000 Wei हैं, तो क्या वह 3.14 ETH है? 31.41 ETH? 3,141 ETH? ईथर के मामले में इसे ETH के लिए 10^18 Wei परिभाषित किया गया है, लेकिन अपने टोकन के लिए आप एक अलग मान चुन सकते हैं। यदि टोकन को विभाजित करने का कोई मतलब नहीं है, तो आप शून्य का _decimals मान उपयोग कर सकते हैं। यदि आप ETH के समान मानक का उपयोग करना चाहते हैं, तो 18 मान का उपयोग करें।

कंस्ट्रक्टर

जब अनुबंध पहली बार बनाया जाता है तो कंस्ट्रक्टर को कॉल किया जाता है। परंपरा के अनुसार, फ़ंक्शन पैरामीटर का नाम <something>_ रखा जाता है।

उपयोगकर्ता इंटरफ़ेस फ़ंक्शंस

ये फ़ंक्शंस, name, symbol, और decimals उपयोगकर्ता इंटरफ़ेस को आपके अनुबंध के बारे में जानने में मदद करते हैं ताकि वे इसे ठीक से प्रदर्शित कर सकें।

रिटर्न प्रकार string memory है, जिसका अर्थ है एक स्ट्रिंग लौटाना जो मेमोरी में संग्रहीत है। चर, जैसे कि स्ट्रिंग, तीन स्थानों पर संग्रहीत किए जा सकते हैं:

जीवनकाल (Lifetime)अनुबंध पहुंच (Contract Access)गैस लागत (Gas Cost)
Memoryफ़ंक्शन कॉलपढ़ें/लिखेंदहाई या सैकड़ा (उच्च स्थानों के लिए अधिक)
Calldataफ़ंक्शन कॉलकेवल पढ़ेंरिटर्न प्रकार के रूप में उपयोग नहीं किया जा सकता, केवल फ़ंक्शन पैरामीटर प्रकार
Storageबदलने तकपढ़ें/लिखेंउच्च (पढ़ने के लिए 800, लिखने के लिए 20k)

इस मामले में, memory सबसे अच्छा विकल्प है।

टोकन जानकारी पढ़ें

ये ऐसे फ़ंक्शंस हैं जो टोकन के बारे में जानकारी प्रदान करते हैं, या तो कुल आपूर्ति या किसी खाते का बैलेंस।

    /**
     * @dev {IERC20-totalSupply} देखें।
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

totalSupply फ़ंक्शन टोकन की कुल आपूर्ति लौटाता है।

 

    /**
     * @dev {IERC20-balanceOf} देखें।
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

किसी खाते का बैलेंस पढ़ें। ध्यान दें कि किसी को भी किसी अन्य के खाते का बैलेंस प्राप्त करने की अनुमति है। इस जानकारी को छिपाने की कोशिश करने का कोई मतलब नहीं है, क्योंकि यह वैसे भी हर नोड पर उपलब्ध है। ब्लॉकचेन पर कोई रहस्य नहीं हैं।

टोकन ट्रांसफर करें

transfer फ़ंक्शन को प्रेषक के खाते से किसी भिन्न खाते में टोकन ट्रांसफर करने के लिए कॉल किया जाता है। ध्यान दें कि भले ही यह एक बूलियन मान लौटाता है, वह मान हमेशा true होता है। यदि ट्रांसफर विफल हो जाता है तो अनुबंध कॉल को रिवर्ट कर देता है।

 

        _transfer(_msgSender(), recipient, amount);
        return true;
    }

_transfer फ़ंक्शन वास्तविक काम करता है। यह एक निजी फ़ंक्शन है जिसे केवल अन्य अनुबंध फ़ंक्शंस द्वारा कॉल किया जा सकता है। परंपरा के अनुसार निजी फ़ंक्शंस का नाम _<something> रखा जाता है, जो स्थिति चरों के समान है।

आमतौर पर Solidity में हम संदेश प्रेषक के लिए msg.sender का उपयोग करते हैं। हालाँकि, यह OpenGSN (opens in a new tab) को तोड़ देता है। यदि हम अपने टोकन के साथ ईथर-रहित लेन-देन की अनुमति देना चाहते हैं, तो हमें _msgSender() का उपयोग करने की आवश्यकता है। यह सामान्य लेन-देन के लिए msg.sender लौटाता है, लेकिन ईथर-रहित लेन-देन के लिए मूल हस्ताक्षरकर्ता को लौटाता है न कि उस अनुबंध को जिसने संदेश को रिले किया था।

व्यय सीमा फ़ंक्शंस

ये वे फ़ंक्शंस हैं जो व्यय सीमा कार्यक्षमता को लागू करते हैं: allowance, approve, transferFrom, और _approve। इसके अतिरिक्त, ओपनजेपेलिन कार्यान्वयन बुनियादी मानक से आगे बढ़कर कुछ ऐसी सुविधाएँ शामिल करता है जो सुरक्षा में सुधार करती हैं: increaseAllowance, और decreaseAllowance

allowance फ़ंक्शन

    /**
     * @dev {IERC20-allowance} देखें।
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

allowance फ़ंक्शन सभी को किसी भी व्यय सीमा की जांच करने की अनुमति देता है।

approve फ़ंक्शन

    /**
     * @dev {IERC20-approve} देखें।
     *
     * आवश्यकताएँ:
     *
     * - `spender` शून्य पता नहीं हो सकता है।
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {

इस फ़ंक्शन को व्यय सीमा बनाने के लिए कॉल किया जाता है। यह ऊपर दिए गए transfer फ़ंक्शन के समान है:

  • फ़ंक्शन केवल एक आंतरिक फ़ंक्शन (इस मामले में, _approve) को कॉल करता है जो वास्तविक काम करता है।
  • फ़ंक्शन या तो true लौटाता है (यदि सफल हो) या रिवर्ट करता है (यदि नहीं)।

 

        _approve(_msgSender(), spender, amount);
        return true;
    }

हम उन स्थानों की संख्या को कम करने के लिए आंतरिक फ़ंक्शंस का उपयोग करते हैं जहाँ स्थिति में परिवर्तन होते हैं। कोई भी फ़ंक्शन जो स्थिति को बदलता है वह एक संभावित सुरक्षा जोखिम है जिसका सुरक्षा के लिए ऑडिट किया जाना चाहिए। इस तरह हमारे पास गलतियाँ करने की संभावना कम होती है।

transferFrom फ़ंक्शन

यह वह फ़ंक्शन है जिसे खर्च करने वाला व्यय सीमा खर्च करने के लिए कॉल करता है। इसके लिए दो ऑपरेशनों की आवश्यकता होती है: खर्च की जा रही राशि को ट्रांसफर करना और व्यय सीमा को उस राशि से कम करना।

 

a.sub(b, "message") फ़ंक्शन कॉल दो काम करता है। पहला, यह a-b की गणना करता है, जो नई व्यय सीमा है। दूसरा, यह जांचता है कि यह परिणाम नकारात्मक तो नहीं है। यदि यह नकारात्मक है तो कॉल दिए गए संदेश के साथ रिवर्ट हो जाती है। ध्यान दें कि जब कोई कॉल रिवर्ट होती है तो उस कॉल के दौरान पहले की गई किसी भी प्रोसेसिंग को अनदेखा कर दिया जाता है, इसलिए हमें _transfer को पूर्ववत (undo) करने की आवश्यकता नहीं है।

        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
             "ERC20: transfer amount exceeds allowance"));
        return true;
    }

ओपनजेपेलिन सुरक्षा परिवर्धन

एक गैर-शून्य व्यय सीमा को दूसरे गैर-शून्य मान पर सेट करना खतरनाक है, क्योंकि आप केवल अपने स्वयं के लेन-देन के क्रम को नियंत्रित करते हैं, किसी और के नहीं। कल्पना करें कि आपके पास दो उपयोगकर्ता हैं, ऐलिस जो भोली है और बिल जो बेईमान है। ऐलिस बिल से कुछ सेवा चाहती है, जिसकी कीमत उसे लगता है कि पांच टोकन है - इसलिए वह बिल को पांच टोकन की व्यय सीमा देती है।

फिर कुछ बदलता है और बिल की कीमत बढ़कर दस टोकन हो जाती है। ऐलिस, जो अभी भी सेवा चाहती है, एक लेन-देन भेजती है जो बिल की व्यय सीमा को दस पर सेट करता है। जैसे ही बिल लेन-देन पूल में इस नए लेन-देन को देखता है, वह एक लेन-देन भेजता है जो ऐलिस के पांच टोकन खर्च करता है और इसका गैस मूल्य बहुत अधिक होता है ताकि इसे तेजी से माइन किया जा सके। इस तरह बिल पहले पांच टोकन खर्च कर सकता है और फिर, एक बार ऐलिस की नई व्यय सीमा माइन हो जाने पर, कुल पंद्रह टोकन की कीमत के लिए दस और खर्च कर सकता है, जो ऐलिस द्वारा अधिकृत किए गए से अधिक है। इस तकनीक को फ्रंट-रनिंग (opens in a new tab) कहा जाता है।

ऐलिस लेन-देनऐलिस नॉन्सबिल लेन-देनबिल नॉन्सबिल की व्यय सीमाऐलिस से बिल की कुल आय
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
approve(Bill, 10)11105
transferFrom(Alice, Bill, 10)10,124015

इस समस्या से बचने के लिए, ये दो फ़ंक्शंस (increaseAllowance और decreaseAllowance) आपको एक विशिष्ट राशि से व्यय सीमा को संशोधित करने की अनुमति देते हैं। इसलिए यदि बिल ने पहले ही पांच टोकन खर्च कर दिए हैं, तो वह केवल पांच और खर्च कर पाएगा। समय के आधार पर, इसके काम करने के दो तरीके हैं, जिनमें से दोनों का अंत बिल को केवल दस टोकन मिलने के साथ होता है:

A:

ऐलिस लेन-देनऐलिस नॉन्सबिल लेन-देनबिल नॉन्सबिल की व्यय सीमाऐलिस से बिल की कुल आय
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
increaseAllowance(Bill, 5)110+5 = 55
transferFrom(Alice, Bill, 5)10,124010

B:

ऐलिस लेन-देनऐलिस नॉन्सबिल लेन-देनबिल नॉन्सबिल की व्यय सीमाऐलिस से बिल की कुल आय
approve(Bill, 5)1050
increaseAllowance(Bill, 5)115+5 = 100
transferFrom(Alice, Bill, 10)10,124010

a.add(b) फ़ंक्शन एक सुरक्षित जोड़ है। इस असंभावित मामले में कि a+b>=2^256 यह उस तरह से रैप अराउंड नहीं करता है जैसे सामान्य जोड़ करता है।

टोकन जानकारी को संशोधित करने वाले फ़ंक्शंस

ये चार फ़ंक्शंस हैं जो वास्तविक काम करते हैं: _transfer, _mint, _burn, और _approve

_transfer फ़ंक्शन

यह फ़ंक्शन, _transfer, एक खाते से दूसरे खाते में टोकन ट्रांसफर करता है। इसे transfer (प्रेषक के स्वयं के खाते से ट्रांसफर के लिए) और transferFrom (किसी और के खाते से ट्रांसफर करने के लिए व्यय सीमा का उपयोग करने के लिए) दोनों द्वारा कॉल किया जाता है।

 

        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

इथेरियम में वास्तव में शून्य पता किसी के पास नहीं है (अर्थात, कोई भी ऐसी निजी कुंजी नहीं जानता है जिसकी मिलान वाली सार्वजनिक कुंजी शून्य पते में बदल जाती है)। जब लोग उस पते का उपयोग करते हैं, तो यह आमतौर पर एक सॉफ़्टवेयर बग होता है - इसलिए यदि शून्य पते का उपयोग प्रेषक या प्राप्तकर्ता के रूप में किया जाता है तो हम विफल हो जाते हैं।

 

        _beforeTokenTransfer(sender, recipient, amount);

इस अनुबंध का उपयोग करने के दो तरीके हैं:

  1. इसे अपने स्वयं के कोड के लिए एक टेम्पलेट के रूप में उपयोग करें
  2. इससे विरासत (inherit) लें (opens in a new tab), और केवल उन फ़ंक्शंस को ओवरराइड करें जिन्हें आपको संशोधित करने की आवश्यकता है

दूसरी विधि बहुत बेहतर है क्योंकि ओपनजेपेलिन ERC-20 कोड का पहले ही ऑडिट किया जा चुका है और इसे सुरक्षित दिखाया गया है। जब आप विरासत का उपयोग करते हैं तो यह स्पष्ट होता है कि आप किन फ़ंक्शंस को संशोधित करते हैं, और आपके अनुबंध पर भरोसा करने के लिए लोगों को केवल उन विशिष्ट फ़ंक्शंस का ऑडिट करने की आवश्यकता होती है।

हर बार टोकन के हाथ बदलने पर एक फ़ंक्शन निष्पादित करना अक्सर उपयोगी होता है। हालाँकि, _transfer एक बहुत ही महत्वपूर्ण फ़ंक्शन है और इसे असुरक्षित रूप से लिखना संभव है (नीचे देखें), इसलिए इसे ओवरराइड न करना ही सबसे अच्छा है। समाधान _beforeTokenTransfer है, जो एक हुक फ़ंक्शन (opens in a new tab) है। आप इस फ़ंक्शन को ओवरराइड कर सकते हैं, और इसे प्रत्येक ट्रांसफर पर कॉल किया जाएगा।

 

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);

ये वे पंक्तियाँ हैं जो वास्तव में ट्रांसफर करती हैं। ध्यान दें कि उनके बीच कुछ भी नहीं है, और हम प्राप्तकर्ता में जोड़ने से पहले प्रेषक से ट्रांसफर की गई राशि घटाते हैं। यह महत्वपूर्ण है क्योंकि यदि बीच में किसी भिन्न अनुबंध को कॉल किया गया होता, तो इसका उपयोग इस अनुबंध को धोखा देने के लिए किया जा सकता था। इस तरह ट्रांसफर परमाणु (atomic) है, इसके बीच में कुछ भी नहीं हो सकता है।

 

        emit Transfer(sender, recipient, amount);
    }

अंत में, एक Transfer घटना उत्सर्जित करें। घटनाएँ स्मार्ट अनुबंधों के लिए सुलभ नहीं हैं, लेकिन ब्लॉकचेन के बाहर चलने वाला कोड घटनाओं को सुन सकता है और उन पर प्रतिक्रिया कर सकता है। उदाहरण के लिए, एक वॉलेट इस बात का ट्रैक रख सकता है कि मालिक को कब अधिक टोकन मिलते हैं।

_mint और _burn फ़ंक्शंस

ये दो फ़ंक्शंस (_mint और _burn) टोकन की कुल आपूर्ति को संशोधित करते हैं। वे आंतरिक हैं और इस अनुबंध में ऐसा कोई फ़ंक्शन नहीं है जो उन्हें कॉल करता हो, इसलिए वे केवल तभी उपयोगी होते हैं जब आप अनुबंध से विरासत लेते हैं और यह तय करने के लिए अपना स्वयं का तर्क जोड़ते हैं कि किन परिस्थितियों में नए टोकन मिंट करने हैं या मौजूदा टोकन बर्न करने हैं।

नोट: प्रत्येक ERC-20 टोकन का अपना व्यावसायिक तर्क होता है जो टोकन प्रबंधन को निर्देशित करता है। उदाहरण के लिए, एक निश्चित आपूर्ति अनुबंध केवल कंस्ट्रक्टर में _mint को कॉल कर सकता है और कभी भी _burn को कॉल नहीं कर सकता है। एक अनुबंध जो टोकन बेचता है वह भुगतान किए जाने पर _mint को कॉल करेगा, और संभवतः अनियंत्रित मुद्रास्फीति से बचने के लिए किसी बिंदु पर _burn को कॉल करेगा।

टोकन की कुल संख्या बदलने पर _totalSupply को अपडेट करना सुनिश्चित करें।

 

_burn फ़ंक्शन लगभग _mint के समान है, सिवाय इसके कि यह दूसरी दिशा में जाता है।

_approve फ़ंक्शन

यह वह फ़ंक्शन है जो वास्तव में व्यय सीमाओं को निर्दिष्ट करता है। ध्यान दें कि यह एक मालिक को ऐसी व्यय सीमा निर्दिष्ट करने की अनुमति देता है जो मालिक के वर्तमान बैलेंस से अधिक है। यह ठीक है क्योंकि बैलेंस की जांच ट्रांसफर के समय की जाती है, जब यह व्यय सीमा बनाए जाने के समय के बैलेंस से भिन्न हो सकता है।

 

एक Approval घटना उत्सर्जित करें। एप्लिकेशन कैसे लिखा गया है, इसके आधार पर, खर्च करने वाले अनुबंध को स्वीकृति के बारे में या तो मालिक द्वारा या इन घटनाओं को सुनने वाले सर्वर द्वारा बताया जा सकता है।

        emit Approval(owner, spender, amount);
    }

Decimals चर को संशोधित करें

यह फ़ंक्शन _decimals चर को संशोधित करता है जिसका उपयोग उपयोगकर्ता इंटरफ़ेस को यह बताने के लिए किया जाता है कि राशि की व्याख्या कैसे करें। आपको इसे कंस्ट्रक्टर से कॉल करना चाहिए। इसे किसी भी बाद के बिंदु पर कॉल करना बेईमानी होगी, और एप्लिकेशन इसे संभालने के लिए डिज़ाइन नहीं किए गए हैं।

हुक्स (Hooks)

यह ट्रांसफर के दौरान कॉल किया जाने वाला हुक फ़ंक्शन है। यह यहाँ खाली है, लेकिन यदि आपको इससे कुछ करवाने की आवश्यकता है तो आप बस इसे ओवरराइड कर दें।

निष्कर्ष

समीक्षा के लिए, इस अनुबंध में कुछ सबसे महत्वपूर्ण विचार यहां दिए गए हैं (मेरी राय में, आपकी राय भिन्न हो सकती है):

  • ब्लॉकचेन पर कोई रहस्य नहीं हैं। कोई भी जानकारी जिस तक स्मार्ट अनुबंध पहुंच सकता है वह पूरी दुनिया के लिए उपलब्ध है।
  • आप अपने स्वयं के लेन-देन के क्रम को नियंत्रित कर सकते हैं, लेकिन यह नहीं कि अन्य लोगों के लेन-देन कब होते हैं। यही कारण है कि व्यय सीमा को बदलना खतरनाक हो सकता है, क्योंकि यह खर्च करने वाले को दोनों व्यय सीमाओं का योग खर्च करने देता है।
  • uint256 प्रकार के मान रैप अराउंड होते हैं। दूसरे शब्दों में, 0-1=2^256-1। यदि यह वांछित व्यवहार नहीं है, तो आपको इसकी जांच करनी होगी (या SafeMath लाइब्रेरी का उपयोग करना होगा जो आपके लिए यह करती है)। ध्यान दें कि यह Solidity 0.8.0 (opens in a new tab) में बदल गया है।
  • एक विशिष्ट प्रकार के सभी स्थिति परिवर्तन एक विशिष्ट स्थान पर करें, क्योंकि इससे ऑडिटिंग आसान हो जाती है। यही कारण है कि हमारे पास, उदाहरण के लिए, _approve है, जिसे approve, transferFrom, increaseAllowance, और decreaseAllowance द्वारा कॉल किया जाता है।
  • स्थिति परिवर्तन परमाणु (atomic) होने चाहिए, उनके बीच में किसी अन्य कार्रवाई के बिना (जैसा कि आप _transfer में देख सकते हैं)। ऐसा इसलिए है क्योंकि स्थिति परिवर्तन के दौरान आपके पास एक असंगत स्थिति होती है। उदाहरण के लिए, जब आप प्रेषक के बैलेंस से कटौती करते हैं और जब आप प्राप्तकर्ता के बैलेंस में जोड़ते हैं, उस समय के बीच अस्तित्व में जितने टोकन होने चाहिए उससे कम होते हैं। यदि उनके बीच संचालन होते हैं, विशेष रूप से किसी भिन्न अनुबंध को कॉल, तो इसका संभावित रूप से दुरुपयोग किया जा सकता है।

अब जब आपने देख लिया है कि ओपनजेपेलिन ERC-20 अनुबंध कैसे लिखा जाता है, और विशेष रूप से इसे कैसे अधिक सुरक्षित बनाया जाता है, तो जाएं और अपने स्वयं के सुरक्षित अनुबंध और एप्लिकेशन लिखें।

मेरे और काम के लिए यहां देखें (opens in a new tab)