मुख्य आशयावर जा

ERC-20 कॉन्ट्रॅक्ट वॉक-थ्रू

Solidity
erc-20
नवशिक्या
ओरी पोमेरँट्झ
9 मार्च, 2021
25 मिनिटांचे वाचन

परिचय

इथेरियमचा सर्वात सामान्य वापर म्हणजे एखाद्या गटाने व्यापार करण्यायोग्य टोकन तयार करणे, एका अर्थाने त्यांचे स्वतःचे चलन. ही टोकन्स सामान्यतः एका मानकाचे पालन करतात, ERC-20. हे मानक तरलता पूल आणि वॉलेट्स सारखी साधने लिहिणे शक्य करते, जी सर्व ERC-20 टोकन्ससह कार्य करतात. या लेखामध्ये आपण ओपनझेपलिन Solidity ERC20 अंमलबजावणी (opens in a new tab), तसेच इंटरफेस व्याख्या (opens in a new tab) यांचे विश्लेषण करू.

हा भाष्य केलेला (annotated) सोर्स कोड आहे. जर तुम्हाला 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 फाइल्समध्ये परवाना ओळखकर्ता (license identifier) समाविष्ट असणे अपेक्षित आहे. तुम्ही परवान्यांची यादी येथे पाहू शकता (opens in a new tab). जर तुम्हाला वेगळ्या परवान्याची आवश्यकता असेल, तर ते फक्त टिप्पण्यांमध्ये (comments) स्पष्ट करा.

 

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) चा भाग आहे, जो सोर्स कोडमधून दस्तऐवजीकरण (documentation) तयार करण्यासाठी वापरला जातो.

 

interface IERC20 {

संकेतांनुसार, इंटरफेसची नावे I ने सुरू होतात.

 

    /**
     * @dev अस्तित्वात असलेल्या टोकनची संख्या परत करते.
     */
    function totalSupply() external view returns (uint256);

हे फंक्शन external आहे, याचा अर्थ ते फक्त कॉन्ट्रॅक्टच्या बाहेरून कॉल केले जाऊ शकते (opens in a new tab). ते कॉन्ट्रॅक्टमधील टोकन्सचा एकूण पुरवठा परत करते. हे मूल्य इथेरियममधील सर्वात सामान्य प्रकार, अनसाइन्ड 256 बिट्स (256 बिट्स हा EVM चा मूळ वर्ड आकार आहे) वापरून परत केले जाते. हे फंक्शन एक view देखील आहे, ज्याचा अर्थ असा आहे की ते स्थिती बदलत नाही, त्यामुळे ब्लॉकचेनमधील प्रत्येक नोडवर चालवण्याऐवजी ते एकाच नोडवर कार्यान्वित केले जाऊ शकते. या प्रकारचे फंक्शन व्यवहार तयार करत नाही आणि त्यासाठी गॅस लागत नाही.

टीप: सैद्धांतिकदृष्ट्या असे वाटू शकते की कॉन्ट्रॅक्टचा निर्माता वास्तविक मूल्यापेक्षा कमी एकूण पुरवठा परत करून फसवणूक करू शकतो, ज्यामुळे प्रत्येक टोकन प्रत्यक्षात असल्यापेक्षा अधिक मौल्यवान वाटू शकते. तथापि, ही भीती ब्लॉकचेनच्या खऱ्या स्वरूपाकडे दुर्लक्ष करते. ब्लॉकचेनवर घडणारी प्रत्येक गोष्ट प्रत्येक नोडद्वारे सत्यापित केली जाऊ शकते. हे साध्य करण्यासाठी, प्रत्येक कॉन्ट्रॅक्टचा मशीन लँग्वेज कोड आणि स्टोरेज प्रत्येक नोडवर उपलब्ध असते. जरी तुम्हाला तुमच्या कॉन्ट्रॅक्टसाठी Solidity कोड प्रकाशित करणे आवश्यक नसले तरी, जोपर्यंत तुम्ही सोर्स कोड आणि ज्या Solidity आवृत्तीसह तो संकलित (compiled) केला गेला आहे ती प्रकाशित करत नाही तोपर्यंत कोणीही तुम्हाला गांभीर्याने घेणार नाही, जेणेकरून तुम्ही प्रदान केलेल्या मशीन लँग्वेज कोडच्या विरूद्ध त्याची पडताळणी केली जाऊ शकेल. उदाहरणार्थ, हे कॉन्ट्रॅक्ट (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, देखील उत्सर्जित (emit) करते.

दोन वेगवेगळ्या प्रकारच्या कॉलर्ससाठी फंक्शनचे दोन प्रकारचे आउटपुट असतात:

  • वापरकर्ते जे थेट युजर इंटरफेसमधून फंक्शनला कॉल करतात. सामान्यतः वापरकर्ता व्यवहार सबमिट करतो आणि प्रतिसादाची वाट पाहत नाही, ज्याला अनिश्चित वेळ लागू शकतो. वापरकर्ता व्यवहार पावती (जी व्यवहार हॅशद्वारे ओळखली जाते) शोधून किंवा Transfer घटना शोधून काय झाले ते पाहू शकतो.
  • इतर कॉन्ट्रॅक्ट्स, जे एकूण व्यवहाराचा भाग म्हणून फंक्शनला कॉल करतात. त्या कॉन्ट्रॅक्ट्सना त्वरित परिणाम मिळतो, कारण ते एकाच व्यवहारामध्ये चालतात, त्यामुळे ते फंक्शन रिटर्न व्हॅल्यू वापरू शकतात.

कॉन्ट्रॅक्टची स्थिती बदलणाऱ्या इतर फंक्शन्सद्वारे समान प्रकारचे आउटपुट तयार केले जाते.

 

मंजुरी एखाद्या खात्याला वेगळ्या मालकाच्या मालकीचे काही टोकन्स खर्च करण्याची परवानगी देते. हे उपयुक्त आहे, उदाहरणार्थ, विक्रेते म्हणून काम करणाऱ्या कॉन्ट्रॅक्ट्ससाठी. कॉन्ट्रॅक्ट्स घटनांवर लक्ष ठेवू शकत नाहीत, त्यामुळे जर खरेदीदाराने थेट विक्रेता कॉन्ट्रॅक्टमध्ये टोकन्स हस्तांतरित केले तर त्या कॉन्ट्रॅक्टला पैसे दिले गेल्याचे समजणार नाही. त्याऐवजी, खरेदीदार विक्रेता कॉन्ट्रॅक्टला ठराविक रक्कम खर्च करण्याची परवानगी देतो आणि विक्रेता ती रक्कम हस्तांतरित करतो. हे विक्रेता कॉन्ट्रॅक्ट कॉल करत असलेल्या फंक्शनद्वारे केले जाते, जेणेकरून विक्रेता कॉन्ट्रॅक्टला ते यशस्वी झाले की नाही हे समजू शकेल.

    /**
     * @dev `spender` ला `owner` च्या वतीने {transferFrom} द्वारे खर्च करण्यासाठी परवानगी असलेल्या उर्वरित टोकनची संख्या परत करते. हे डीफॉल्टनुसार शून्य असते.
     *
     * जेव्हा {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 "../../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) शोधू शकता.

व्हेरिएबल व्याख्या

या व्याख्या कॉन्ट्रॅक्टचे स्थिती व्हेरिएबल्स निर्दिष्ट करतात. हे व्हेरिएबल्स private घोषित केले आहेत, परंतु याचा अर्थ असा आहे की ब्लॉकचेनवरील इतर कॉन्ट्रॅक्ट्स ते वाचू शकत नाहीत. ब्लॉकचेनवर कोणतीही गुपिते नाहीत, प्रत्येक नोडवरील सॉफ्टवेअरमध्ये प्रत्येक ब्लॉकवर प्रत्येक कॉन्ट्रॅक्टची स्थिती असते. संकेतांनुसार, स्थिती व्हेरिएबल्सना _<something> असे नाव दिले जाते.

पहिले दोन व्हेरिएबल्स मॅपिंग्ज (opens in a new tab) आहेत, याचा अर्थ ते साधारणपणे असोसिएटिव्ह ॲरे (opens in a new tab) प्रमाणेच वागतात, फक्त कीज (keys) संख्यात्मक मूल्ये असतात. स्टोरेज फक्त त्या नोंदींसाठी वाटप केले जाते ज्यांची मूल्ये डीफॉल्ट (शून्य) पेक्षा वेगळी असतात.

    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 हे अंदाजे एक US किंवा युरो सेंट आहे.

ॲप्लिकेशन्सना टोकन शिल्लक कशी प्रदर्शित करायची हे माहित असणे आवश्यक आहे. जर एखाद्या वापरकर्त्याकडे 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)कॉन्ट्रॅक्ट ॲक्सेसगॅसची किंमत
मेमरीफंक्शन कॉलरीड/राइटदहा किंवा शेकडो (उच्च स्थानांसाठी जास्त)
कॉल डेटाफंक्शन कॉलफक्त रीडरिटर्न प्रकार म्हणून वापरले जाऊ शकत नाही, फक्त फंक्शन पॅरामीटर प्रकार
स्टोरेजबदलेपर्यंतरीड/राइटउच्च (रीडसाठी 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 पूर्ववत करण्याची आवश्यकता नाही.

        _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 असण्याच्या दुर्मिळ घटनेत ते सामान्य बेरीज ज्या प्रकारे रॅप अराउंड (wrap around) करते तसे करत नाही.

टोकन माहिती सुधारणारी फंक्शन्स

ही चार फंक्शन्स आहेत जी प्रत्यक्ष काम करतात: _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. त्यातून इनहेरिट करा (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 व्हेरिएबल सुधारते ज्याचा वापर युजर इंटरफेसला रकमेचा अर्थ कसा लावायचा हे सांगण्यासाठी केला जातो. तुम्ही त्याला कन्स्ट्रक्टरमधून कॉल केले पाहिजे. त्यानंतरच्या कोणत्याही टप्प्यावर त्याला कॉल करणे अप्रामाणिकपणाचे ठरेल, आणि ॲप्लिकेशन्स ते हाताळण्यासाठी डिझाइन केलेले नाहीत.

हूक्स

हस्तांतरणादरम्यान कॉल केले जाणारे हे हूक फंक्शन आहे. ते येथे रिकामे आहे, परंतु जर तुम्हाला त्याने काहीतरी करावे असे वाटत असेल तर तुम्ही फक्त ते ओव्हरराइड करा.

निष्कर्ष

पुनरावलोकनासाठी, या कॉन्ट्रॅक्टमधील काही सर्वात महत्त्वाच्या कल्पना येथे आहेत (माझ्या मते, तुमच्या भिन्न असण्याची शक्यता आहे):

  • ब्लॉकचेनवर कोणतीही गुपिते नाहीत. स्मार्ट कॉन्ट्रॅक्ट ॲक्सेस करू शकणारी कोणतीही माहिती संपूर्ण जगासाठी उपलब्ध असते.
  • तुम्ही तुमच्या स्वतःच्या व्यवहारांचा क्रम नियंत्रित करू शकता, परंतु इतर लोकांचे व्यवहार कधी होतात हे नाही. हेच कारण आहे की मंजुरी बदलणे धोकादायक असू शकते, कारण ते खर्च करणाऱ्याला दोन्ही मंजुरींची बेरीज खर्च करू देते.
  • uint256 प्रकाराची मूल्ये रॅप अराउंड होतात. दुसऱ्या शब्दांत, 0-1=2^256-1. जर ते अपेक्षित वर्तन नसेल, तर तुम्हाला ते तपासावे लागेल (किंवा SafeMath लायब्ररी वापरा जी तुमच्यासाठी ते करते). लक्षात घ्या की हे Solidity 0.8.0 (opens in a new tab) मध्ये बदलले आहे.
  • विशिष्ट प्रकारच्या सर्व स्थितीतील बदल एका विशिष्ट ठिकाणी करा, कारण यामुळे ऑडिट करणे सोपे होते. हेच कारण आहे की आपल्याकडे, उदाहरणार्थ, _approve आहे, ज्याला approve, transferFrom, increaseAllowance, आणि decreaseAllowance द्वारे कॉल केले जाते.
  • स्थितीतील बदल ॲटॉमिक असले पाहिजेत, त्यांच्या मध्यभागी इतर कोणतीही कृती नसावी (जसे तुम्ही _transfer मध्ये पाहू शकता). याचे कारण असे की स्थिती बदलताना तुमच्याकडे विसंगत स्थिती असते. उदाहरणार्थ, तुम्ही प्रेषकाच्या शिल्लकीतून वजा करता त्या वेळेच्या आणि प्राप्तकर्त्याच्या शिल्लकीत जोडता त्या वेळेच्या दरम्यान अस्तित्वात असावेत त्यापेक्षा कमी टोकन असतात. जर त्यांच्यामध्ये ऑपरेशन्स असतील, विशेषतः वेगळ्या कॉन्ट्रॅक्टला कॉल्स असतील, तर याचा संभाव्य गैरवापर होऊ शकतो.

आता तुम्ही पाहिले आहे की ओपनझेपलिन ERC-20 कॉन्ट्रॅक्ट कसे लिहिले जाते, आणि विशेषतः ते अधिक सुरक्षित कसे बनवले जाते, जा आणि तुमचे स्वतःचे सुरक्षित कॉन्ट्रॅक्ट्स आणि ॲप्लिकेशन्स लिहा.

माझ्या अधिक कामासाठी येथे पहा (opens in a new tab).