ERC-20 सुरक्षा रेल्ससह
प्रस्तावना
Ethereum बद्दल एक मोठी चांगली गोष्ट म्हणजे अशी कोणतीही केंद्रीय सत्ता नाही जी तुमचे व्यवहार बदलू किंवा पूर्ववत करू शकेल. Ethereum मधील एक मोठी समस्या अशी आहे की वापरकर्त्याच्या चुका किंवा अवैध व्यवहार पूर्ववत करण्याची शक्ती असलेली कोणतीही केंद्रीय सत्ता नाही. या लेखात तुम्ही ERC-20 टोकन्ससह वापरकर्ते करत असलेल्या काही सामान्य चुकांबद्दल शिकाल, तसेच अशा ERC-20 कॉन्ट्रॅक्ट्स कसे तयार करावे जे वापरकर्त्यांना त्या चुका टाळण्यास मदत करतात, किंवा जे केंद्रीय सत्तेला काही अधिकार देतात (उदाहरणार्थ, खाती फ्रीझ करणे).
लक्षात घ्या की आम्ही OpenZeppelin ERC-20 टोकन कॉन्ट्रॅक्ट (opens in a new tab) वापरणार असलो तरी, हा लेख त्याचे तपशीलवार स्पष्टीकरण देत नाही. तुम्ही ही माहिती येथे शोधू शकता.
तुम्हाला संपूर्ण सोर्स कोड पहायचा असल्यास:
- Remix IDE (opens in a new tab) उघडा.
- क्लोन गिटहब आयकॉनवर क्लिक करा (
). - गिटहब रिपॉझिटरी
https://github.com/qbzzt/20220815-erc20-safety-railsक्लोन करा. - contracts > erc20-safety-rails.sol उघडा.
ERC-20 कॉन्ट्रॅक्ट तयार करणे
आपण सुरक्षा रेलची कार्यक्षमता जोडण्यापूर्वी, आपल्याला एका ERC-20 कॉन्ट्रॅक्टची आवश्यकता आहे. या लेखात आम्ही OpenZeppelin Contracts Wizard (opens in a new tab) वापरणार आहोत. ते दुसऱ्या ब्राउझरमध्ये उघडा आणि या सूचनांचे पालन करा:
-
ERC20 निवडा.
-
या सेटिंग्ज प्रविष्ट करा:
पॅरामीटर मूल्य नाव SafetyRailsToken चिन्ह SAFE Premint आम्हाला प्रति टोकन जोडीसाठी एकापेक्षा जास्त लिक्विडिटी पूल नको आहे. वैशिष्ट्ये काहीही नाही ॲक्सेस कंट्रोल Ownable अपग्रेडेबिलिटी काहीही नाही -
वर स्क्रोल करा आणि भिन्न पर्यावरण वापरण्यासाठी Open in Remix (Remix साठी) किंवा Download वर क्लिक करा. मी असे गृहीत धरतो की तुम्ही Remix वापरत आहात, तुम्ही दुसरे काही वापरल्यास, योग्य बदल करा.
-
आमच्याकडे आता पूर्णपणे कार्यरत ERC-20 कॉन्ट्रॅक्ट आहे. इम्पोर्ट केलेला कोड पाहण्यासाठी तुम्ही
.deps>npmचा विस्तार करू शकता. -
ते ERC-20 कॉन्ट्रॅक्ट म्हणून कार्य करते हे पाहण्यासाठी कॉन्ट्रॅक्ट कंपाईल करा, उपयोजित करा आणि त्याच्याशी खेळा. तुम्हाला Remix कसे वापरावे हे शिकायचे असल्यास, हे ट्यूटोरियल वापरा (opens in a new tab).
सामान्य चुका
चुका
वापरकर्ते कधीकधी चुकीच्या पत्त्यावर टोकन पाठवतात. त्यांना काय करायचे होते हे जाणून घेण्यासाठी आम्ही त्यांचे मन वाचू शकत नसलो तरी, दोन प्रकारच्या त्रुटी आहेत ज्या खूप वेळा घडतात आणि ओळखण्यास सोप्या आहेत:
-
टोकन कॉन्ट्रॅक्टच्या स्वतःच्या पत्त्यावर पाठवणे. उदाहरणार्थ, Optimism's OP token (opens in a new tab) ने दोन महिन्यांपेक्षा कमी कालावधीत 120,000 पेक्षा जास्त (opens in a new tab) OP टोकन जमा केले. हे मोठ्या प्रमाणात संपत्ती दर्शवते जी लोकांनी कदाचित गमावली आहे.
-
टोकन रिकाम्या पत्त्यावर पाठवणे, जो बाह्य मालकीच्या खात्याशी किंवा स्मार्ट कॉन्ट्रॅक्टशी संबंधित नाही. हे किती वेळा घडते याची माझ्याकडे आकडेवारी नसली तरी, एका घटनेत 20,000,000 टोकन खर्च होऊ शकले असते (opens in a new tab).
हस्तांतरण प्रतिबंधित करणे
OpenZeppelin ERC-20 कॉन्ट्रॅक्टमध्ये एक हुक, _beforeTokenTransfer (opens in a new tab) समाविष्ट आहे, जो टोकन हस्तांतरित होण्यापूर्वी कॉल केला जातो. डीफॉल्टनुसार हा हुक काहीही करत नाही, परंतु आम्ही त्यावर आमची स्वतःची कार्यक्षमता टांगू शकतो, जसे की समस्या असल्यास उलटणारे तपास.
हुक वापरण्यासाठी, कन्स्ट्रक्टरनंतर हे फंक्शन जोडा:
1 function _beforeTokenTransfer(address from, address to, uint256 amount)2 internal virtual3 override(ERC20)4 {5 super._beforeTokenTransfer(from, to, amount);6 }जर तुम्ही Solidity शी फारसे परिचित नसाल तर या फंक्शनचे काही भाग नवीन असू शकतात:
1 internal virtualvirtual कीवर्डचा अर्थ असा आहे की जसे आम्ही ERC20 कडून कार्यक्षमता मिळवली आणि हे फंक्शन ओव्हरराइड केले, त्याचप्रमाणे इतर कॉन्ट्रॅक्ट्स आमच्याकडून वारसा घेऊ शकतात आणि हे फंक्शन ओव्हरराइड करू शकतात.
1 override(ERC20)आम्हाला स्पष्टपणे नमूद करावे लागेल की आम्ही _beforeTokenTransfer ची ERC20 टोकन व्याख्या ओव्हरराइड (opens in a new tab) करत आहोत. सर्वसाधारणपणे, सुरक्षिततेच्या दृष्टिकोनातून, स्पष्ट व्याख्या अंतर्निहित व्याख्यांपेक्षा खूपच चांगल्या आहेत - तुम्ही तुमच्या समोर काहीतरी केले असल्यास ते विसरू शकत नाही. हेच कारण आहे की आम्हाला कोणत्या सुपरक्लासचा _beforeTokenTransfer ओव्हरराइड करत आहोत हे निर्दिष्ट करणे आवश्यक आहे.
1 super._beforeTokenTransfer(from, to, amount);ही ओळ कॉन्ट्रॅक्ट किंवा कॉन्ट्रॅक्ट्सच्या _beforeTokenTransfer फंक्शनला कॉल करते ज्यांच्याकडून आम्ही वारसा घेतला आहे. या प्रकरणात, ते फक्त ERC20 आहे, Ownable कडे हा हुक नाही. सध्या ERC20._beforeTokenTransfer काहीही करत नसले तरी, भविष्यात कार्यक्षमता जोडल्यास आम्ही ते कॉल करतो (आणि मग आम्ही कॉन्ट्रॅक्ट पुन्हा तैनात करण्याचा निर्णय घेतो, कारण उपयोजनानंतर कॉन्ट्रॅक्ट बदलत नाहीत).
आवश्यकतांचे कोडिंग करणे
आम्ही फंक्शनमध्ये या आवश्यकता जोडू इच्छितो:
toपत्ताaddress(this)च्या बरोबर असू शकत नाही, जो ERC-20 कॉन्ट्रॅक्टचा स्वतःचा पत्ता आहे.toपत्ता रिकामा असू शकत नाही, तो यापैकी एक असावा:- बाह्य मालकीचे खाते (EOA). एखादा पत्ता थेट EOA आहे की नाही हे आम्ही तपासू शकत नाही, परंतु आम्ही पत्त्याची ETH शिल्लक तपासू शकतो. EOAs मध्ये जवळजवळ नेहमीच शिल्लक असते, जरी ते आता वापरले जात नसले तरीही - शेवटच्या wei पर्यंत ते साफ करणे कठीण आहे.
- एक स्मार्ट कॉन्ट्रॅक्ट. एखादा पत्ता स्मार्ट कॉन्ट्रॅक्ट आहे की नाही हे तपासणे थोडे कठीण आहे. बाह्य कोडची लांबी तपासणारा एक ऑपकोड आहे, ज्याला
EXTCODESIZE(opens in a new tab) म्हणतात, परंतु तो थेट Solidity मध्ये उपलब्ध नाही. त्यासाठी आम्हाला Yul (opens in a new tab) वापरावे लागेल, जे EVM असेंब्ली आहे. Solidity मधून आम्ही वापरू शकणारी इतर मूल्ये आहेत (<address>.codeआणि<address>.codehash(opens in a new tab)), पण ती अधिक महाग आहेत.
चला नवीन कोड ओळीने ओळ पाहूया:
1 require(to != address(this), "कॉन्ट्रॅक्ट पत्त्यावर टोकन पाठवू शकत नाही");ही पहिली आवश्यकता आहे, to आणि this(address) समान नाहीत हे तपासा.
1 bool isToContract;2 assembly {3 isToContract := gt(extcodesize(to), 0)4 }अशा प्रकारे आम्ही पत्ता कॉन्ट्रॅक्ट आहे की नाही हे तपासतो. आम्ही Yul कडून थेट आउटपुट प्राप्त करू शकत नाही, म्हणून त्याऐवजी आम्ही परिणाम ठेवण्यासाठी एक व्हेरिएबल परिभाषित करतो (या प्रकरणात isToContract). Yul ज्या प्रकारे कार्य करते ते असे आहे की प्रत्येक ऑपकोड एक फंक्शन मानला जातो. म्हणून प्रथम आम्ही कॉन्ट्रॅक्टचा आकार मिळविण्यासाठी EXTCODESIZE (opens in a new tab) कॉल करतो आणि नंतर ते शून्य नाही हे तपासण्यासाठी GT (opens in a new tab) वापरतो (आम्ही अनसाईंड इंटीजर्ससह काम करत आहोत, म्हणून अर्थातच ते नकारात्मक असू शकत नाही). त्यानंतर आम्ही निकाल isToContract मध्ये लिहितो.
1 require(to.balance != 0 || isToContract, "रिकाम्या पत्त्यावर टोकन पाठवू शकत नाही");आणि शेवटी, आमच्याकडे रिकाम्या पत्त्यांसाठी वास्तविक तपासणी आहे.
प्रशासकीय प्रवेश
कधीकधी चुका पूर्ववत करू शकणारा प्रशासक असणे उपयुक्त ठरते. गैरवापराची शक्यता कमी करण्यासाठी, हा प्रशासक एक multisig (opens in a new tab) असू शकतो जेणेकरून एका कृतीवर अनेक लोकांना सहमत व्हावे लागेल. या लेखात आमच्याकडे दोन प्रशासकीय वैशिष्ट्ये असतील:
-
खाती फ्रीझ करणे आणि अनफ्रीझ करणे. हे उपयुक्त असू शकते, उदाहरणार्थ, जेव्हा एखादे खाते धोक्यात आले असेल.
-
मालमत्ता स्वच्छता.
कधीकधी फसवणूक करणारे वैधता मिळविण्यासाठी वास्तविक टोकनच्या कॉन्ट्रॅक्टवर बनावट टोकन पाठवतात. उदाहरणार्थ, येथे पहा (opens in a new tab). कायदेशीर ERC-20 कॉन्ट्रॅक्ट 0x4200....0042 (opens in a new tab) आहे. त्याचा बहाणा करणारा घोटाळा 0x234....bbe (opens in a new tab) आहे.
हे देखील शक्य आहे की लोक चुकून आमच्या कॉन्ट्रॅक्टवर कायदेशीर ERC-20 टोकन पाठवतात, जे त्यांना बाहेर काढण्याचा मार्ग हवा असण्याचे आणखी एक कारण आहे.
OpenZeppelin प्रशासकीय प्रवेश सक्षम करण्यासाठी दोन यंत्रणा प्रदान करते:
Ownable(opens in a new tab) कॉन्ट्रॅक्ट्सचा एकच मालक असतो.onlyOwnermodifier (opens in a new tab) असलेले फंक्शन्स फक्त त्या मालकाद्वारे कॉल केले जाऊ शकतात. मालक मालकी दुसऱ्या कोणाला हस्तांतरित करू शकतात किंवा पूर्णपणे सोडून देऊ शकतात. इतर सर्व खात्यांचे अधिकार सामान्यतः समान असतात.AccessControl(opens in a new tab) कॉन्ट्रॅक्ट्समध्ये भूमिका-आधारित प्रवेश नियंत्रण (RBAC) (opens in a new tab) असते.
साधेपणासाठी, या लेखात आम्ही Ownable वापरतो.
कॉन्ट्रॅक्ट्स फ्रीझ करणे आणि थॉ करणे
कॉन्ट्रॅक्ट्स फ्रीझ करणे आणि थॉ करण्यासाठी अनेक बदलांची आवश्यकता आहे:
-
कोणते पत्ते फ्रीझ केले आहेत याचा मागोवा ठेवण्यासाठी पत्त्यांवरून booleans (opens in a new tab) पर्यंतचे mapping (opens in a new tab). सर्व मूल्ये सुरुवातीला शून्य असतात, ज्याचा अर्थ बुलियन मूल्यांसाठी असत्य (false) असा लावला जातो. हेच आम्हाला हवे आहे कारण डीफॉल्टनुसार खाती फ्रीझ केलेली नसतात.
1 mapping(address => bool) public frozenAccounts; -
एखादे खाते फ्रीझ किंवा थॉ झाल्यावर कोणालाही माहिती देण्यासाठी Events (opens in a new tab). तांत्रिकदृष्ट्या या क्रियांसाठी इव्हेंट्स आवश्यक नाहीत, परंतु ते ऑफचेन कोडला या इव्हेंट्स ऐकण्यास आणि काय होत आहे हे जाणून घेण्यास मदत करते. स्मार्ट कॉन्ट्रॅक्टसाठी जेव्हा दुसऱ्या कोणासाठी संबंधित काहीतरी घडते तेव्हा ते उत्सर्जित करणे चांगले शिष्टाचार मानले जाते.
इव्हेंट्स अनुक्रमित केले जातात जेणेकरून खाते किती वेळा फ्रीझ किंवा थॉ केले गेले आहे हे शोधणे शक्य होईल.
1 // जेव्हा खाती फ्रीझ किंवा अनफ्रीझ केली जातात2 event AccountFrozen(address indexed _addr);3 event AccountThawed(address indexed _addr); -
खाती फ्रीझ आणि थॉ करण्यासाठी फंक्शन्स. ही दोन फंक्शन्स जवळजवळ सारखीच आहेत, म्हणून आपण फक्त फ्रीझ फंक्शन पाहू.
1 function freezeAccount(address addr)2 public3 onlyOwnerpublic(opens in a new tab) म्हणून चिन्हांकित केलेली फंक्शन्स इतर स्मार्ट कॉन्ट्रॅक्ट्समधून किंवा थेट व्यवहाराद्वारे कॉल केली जाऊ शकतात.1 {2 require(!frozenAccounts[addr], "खाते आधीच फ्रीझ आहे");3 frozenAccounts[addr] = true;4 emit AccountFrozen(addr);5 } // freezeAccountजर खाते आधीच फ्रीझ असेल, तर उलट करा. अन्यथा, ते फ्रीझ करा आणि एक इव्हेंट
emitकरा. -
फ्रीझ केलेल्या खात्यातून पैसे हलवण्यापासून रोखण्यासाठी
_beforeTokenTransferबदला. लक्षात घ्या की फ्रीझ केलेल्या खात्यात अजूनही पैसे हस्तांतरित केले जाऊ शकतात.1 require(!frozenAccounts[from], "खाते फ्रीझ आहे");
मालमत्ता स्वच्छता
या कॉन्ट्रॅक्टद्वारे ठेवलेले ERC-20 टोकन सोडण्यासाठी, आम्हाला त्यांच्या टोकन कॉन्ट्रॅक्टवर transfer (opens in a new tab) किंवा approve (opens in a new tab) फंक्शन कॉल करणे आवश्यक आहे. या प्रकरणात भत्त्यांवर गॅस वाया घालवण्यात काही अर्थ नाही, आपण थेट हस्तांतरण करणेच बरे.
1 function cleanupERC20(2 address erc20,3 address dest4 )5 public6 onlyOwner7 {8 IERC20 token = IERC20(erc20);जेव्हा आम्हाला पत्ता मिळतो तेव्हा कॉन्ट्रॅक्टसाठी ऑब्जेक्ट तयार करण्यासाठी हे सिंटॅक्स आहे. आपण हे करू शकतो कारण आमच्याकडे सोर्स कोडचा भाग म्हणून ERC20 टोकनची व्याख्या आहे (ओळ 4 पहा), आणि त्या फाईलमध्ये IERC20 साठीची व्याख्या (opens in a new tab) समाविष्ट आहे, जो OpenZeppelin ERC-20 कॉन्ट्रॅक्टचा इंटरफेस आहे.
1 uint balance = token.balanceOf(address(this));2 token.transfer(dest, balance);3 }हे एक स्वच्छता फंक्शन आहे, म्हणून आम्ही कोणतेही टोकन सोडू इच्छित नाही असे गृहीत धरले जाते. वापरकर्त्याकडून मॅन्युअली शिल्लक मिळवण्याऐवजी, आपण प्रक्रिया स्वयंचलित करणेच बरे.
निष्कर्ष
हा एक परिपूर्ण उपाय नाही - "वापरकर्त्याने चूक केली" या समस्येवर कोणताही परिपूर्ण उपाय नाही. तथापि, या प्रकारच्या तपासण्या वापरून किमान काही चुका टाळता येतात. खाती फ्रीझ करण्याची क्षमता, धोकादायक असली तरी, हॅकरला चोरलेले फंड नाकारून काही हॅक्सचे नुकसान मर्यादित करण्यासाठी वापरली जाऊ शकते.
माझ्या कामाबद्दल अधिक माहितीसाठी येथे पहा (opens in a new tab).
पृष्ठ अखेरचे अद्यतन: 3 मार्च, 2026