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

सेफ्टी रेल्स के साथ ERC-20

erc-20
शुरआती
ओरी पोमेरेंट्ज़
15 अगस्त 2022
10 मिनट का पठन

परिचय

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

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

यदि आप पूरा सोर्स कोड देखना चाहते हैं:

  1. रीमिक्स IDE (opens in a new tab) खोलें।
  2. क्लोन github आइकन (clone github icon) पर क्लिक करें।
  3. Github रिपॉजिटरी https://github.com/qbzzt/20220815-erc20-safety-rails को क्लोन करें।
  4. contracts > erc20-safety-rails.sol खोलें।

एक ERC-20 अनुबंध बनाना

इससे पहले कि हम सेफ्टी रेल कार्यक्षमता जोड़ सकें, हमें एक ERC-20 अनुबंध की आवश्यकता है। इस लेख में हम the ओपनज़ेपेलिन Contracts Wizard (opens in a new tab) का उपयोग करेंगे। इसे दूसरे ब्राउज़र में खोलें और इन निर्देशों का पालन करें:

  1. ERC20 चुनें।

  2. ये सेटिंग्स दर्ज करें:

    पैरामीटरमूल्य
    नामSafetyRailsToken
    प्रतीकSAFE
    प्रीमिंट1000
    विशेषताएँकोई नहीं
    एक्सेस कंट्रोलOwnable
    अपग्रेडेबिलिटीकोई नहीं
  3. ऊपर स्क्रॉल करें और रीमिक्स में खोलें (रीमिक्स के लिए) पर क्लिक करें या एक अलग वातावरण का उपयोग करने के लिए डाउनलोड करें। मैं यह मान लूंगा कि आप रीमिक्स का उपयोग कर रहे हैं, यदि आप कुछ और उपयोग करते हैं तो बस उपयुक्त परिवर्तन करें।

  4. अब हमारे पास एक पूरी तरह से कार्यात्मक ERC-20 अनुबंध है। आयातित कोड देखने के लिए आप .deps > npm का विस्तार कर सकते हैं।

  5. यह देखने के लिए अनुबंध को संकलित करें, डिप्लॉय करें और उसके साथ प्रयोग करें कि यह एक ERC-20 अनुबंध के रूप में कार्य करता है। अगर आपको रीमिक्स का उपयोग करना सीखना है, तो इस ट्यूटोरियल का उपयोग करें (opens in a new tab)

आम गलतियाँ

गलतियाँ

यूज़र कभी-कभी गलत पते पर टोकन भेज देते हैं। हालांकि हम यह जानने के लिए उनके मन को नहीं पढ़ सकते कि वे क्या करना चाहते थे, दो प्रकार की त्रुटियां हैं जो बहुत होती हैं और जिनका पता लगाना आसान है:

  1. टोकन को अनुबंध के अपने पते पर भेजना। उदाहरण के लिए, ऑप्टिमिज़्म का OP टोकन (opens in a new tab) दो महीने से भी कम समय में 120,000 से अधिक (opens in a new tab) OP टोकन जमा करने में कामयाब रहा। यह एक महत्वपूर्ण मात्रा में धन का प्रतिनिधित्व करता है जिसे संभवतः लोगों ने खो दिया है।

  2. टोकन को एक खाली पते पर भेजना, जो बाहरी स्वामित्व वाले खाते या स्मार्ट अनुबंध से मेल नहीं खाता है। हालांकि मेरे पास इस बारे में आंकड़े नहीं हैं कि ऐसा कितनी बार होता है, एक घटना में 20,000,000 टोकन की लागत आ सकती थी (opens in a new tab)

स्थानांतरण को रोकना

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

हुक का उपयोग करने के लिए, कंस्ट्रक्टर के बाद इस फ़ंक्शन को जोड़ें:

1 function _beforeTokenTransfer(address from, address to, uint256 amount)
2 internal virtual
3 override(ERC20)
4 {
5 super._beforeTokenTransfer(from, to, amount);
6 }

यदि आप सॉलिडिटी से बहुत परिचित नहीं हैं तो इस फ़ंक्शन के कुछ हिस्से नए हो सकते हैं:

1 internal virtual

virtual कीवर्ड का मतलब है कि जैसे हमने 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 बैलेंस देख सकते हैं। EOA में लगभग हमेशा एक बैलेंस होता है, भले ही उनका अब उपयोग नहीं किया जाता हो - उन्हें आखिरी wei तक साफ़ करना मुश्किल है।
    • एक स्मार्ट अनुबंध। यह परीक्षण करना कि कोई पता स्मार्ट अनुबंध है या नहीं, थोड़ा कठिन है। एक ऑपकोड है जो बाहरी कोड की लंबाई की जांच करता है, जिसे EXTCODESIZE (opens in a new tab) कहा जाता है, लेकिन यह सीधे सॉलिडिटी में उपलब्ध नहीं है। इसके लिए हमें युल (opens in a new tab) का उपयोग करना होगा, जो कि EVM असेंबली है। सॉलिडिटी से अन्य मान हैं जिनका हम उपयोग कर सकते हैं (<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 }

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

1 require(to.balance != 0 || isToContract, "खाली पते पर टोकन नहीं भेज सकते");

और अंत में, हमारे पास खाली पतों के लिए वास्तविक जांच है।

प्रशासनिक पहुंच

कभी-कभी एक प्रशासक होना उपयोगी होता है जो गलतियों को पूर्ववत कर सकता है। दुरुपयोग की संभावना को कम करने के लिए, यह प्रशासक एक मल्टीसिग (opens in a new tab) हो सकता है ताकि कई लोगों को एक कार्रवाई पर सहमत होना पड़े। इस लेख में हमारे पास दो प्रशासनिक सुविधाएँ होंगी:

  1. खातों को फ्रीज और अनफ्रीज करना। यह उपयोगी हो सकता है, उदाहरण के लिए, जब कोई खाता हैक हो सकता है।

  2. संपत्ति की सफाई।

    कभी-कभी धोखेबाज वैधता हासिल करने के लिए वास्तविक टोकन के अनुबंध में धोखाधड़ी वाले टोकन भेजते हैं। उदाहरण के लिए, यहां देखें (opens in a new tab)। वैध ERC-20 अनुबंध 0x4200....0042 (opens in a new tab) है। घोटाला जो यह होने का नाटक करता है वह 0x234....bbe (opens in a new tab) है।

    यह भी संभव है कि लोग गलती से हमारे अनुबंध में वैध ERC-20 टोकन भेज दें, जो उन्हें बाहर निकालने का एक तरीका चाहने का एक और कारण है।

ओपनज़ेपेलिन प्रशासनिक पहुंच को सक्षम करने के लिए दो तंत्र प्रदान करता है:

सरलता के लिए, इस लेख में हम Ownable का उपयोग करते हैं।

अनुबंधों को फ्रीज और अनफ्रीज करना

अनुबंधों को फ्रीज और अनफ्रीज करने के लिए कई बदलावों की आवश्यकता होती है:

  • बूलियन (opens in a new tab) के लिए पतों से एक मैपिंग (opens in a new tab) यह ट्रैक करने के लिए कि कौन से पते फ्रीज हैं। सभी मान शुरू में शून्य होते हैं, जिसे बूलियन मानों के लिए गलत माना जाता है। यह वही है जो हम चाहते हैं क्योंकि डिफ़ॉल्ट रूप से खाते फ्रीज नहीं होते हैं।

    1 mapping(address => bool) public frozenAccounts;
  • इवेंट्स (opens in a new tab) किसी भी इच्छुक व्यक्ति को सूचित करने के लिए जब कोई खाता फ्रीज या अनफ्रीज किया जाता है। तकनीकी रूप से इन कार्यों के लिए इवेंट्स की आवश्यकता नहीं होती है, लेकिन यह ऑफ-चेन कोड को इन इवेंट्स को सुनने और यह जानने में मदद करता है कि क्या हो रहा है। जब कुछ ऐसा होता है जो किसी और के लिए प्रासंगिक हो सकता है तो स्मार्ट अनुबंध के लिए उन्हें उत्सर्जित करना अच्छा शिष्टाचार माना जाता है।

    इवेंट्स को अनुक्रमित किया जाता है ताकि यह खोजना संभव हो सके कि किसी खाते को कितनी बार फ्रीज या अनफ्रीज किया गया है।

    1 // जब खाते फ्रीज या अनफ्रीज हो जाते हैं
    2 event AccountFrozen(address indexed _addr);
    3 event AccountThawed(address indexed _addr);
  • खातों को फ्रीज और अनफ्रीज करने के लिए फ़ंक्शंस। ये दो फ़ंक्शन लगभग समान हैं, इसलिए हम केवल फ्रीज फ़ंक्शन पर जाएंगे।

    1 function freezeAccount(address addr)
    2 public
    3 onlyOwner

    public (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 dest
4 )
5 public
6 onlyOwner
7 {
8 IERC20 token = IERC20(erc20);

जब हम पता प्राप्त करते हैं तो यह एक अनुबंध के लिए एक ऑब्जेक्ट बनाने के लिए सिंटैक्स है। हम ऐसा इसलिए कर सकते हैं क्योंकि हमारे पास सोर्स कोड के हिस्से के रूप में ERC20 टोकन के लिए परिभाषा है (लाइन 4 देखें), और उस फ़ाइल में IERC20 के लिए परिभाषा (opens in a new tab) शामिल है, जो एक ओपनज़ेपेलिन ERC-20 अनुबंध के लिए इंटरफ़ेस है।

1 uint balance = token.balanceOf(address(this));
2 token.transfer(dest, balance);
3 }

यह एक सफाई फ़ंक्शन है, इसलिए संभवतः हम कोई टोकन नहीं छोड़ना चाहते हैं। यूज़र से मैन्युअल रूप से बैलेंस प्राप्त करने के बजाय, हम प्रक्रिया को स्वचालित भी कर सकते हैं।

निष्कर्ष

यह एक आदर्श समाधान नहीं है - "यूज़र ने गलती की" समस्या का कोई आदर्श समाधान नहीं है। हालांकि, इस तरह की जांच का उपयोग करने से कम से कम कुछ गलतियों को रोका जा सकता है। खातों को फ्रीज करने की क्षमता, जबकि खतरनाक है, हैकर को चोरी किए गए धन से वंचित करके कुछ हैक के नुकसान को सीमित करने के लिए इस्तेमाल किया जा सकता है।

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

पेज का अंतिम अपडेट: 3 मार्च 2026

क्या यह ट्यूटोरियल सहायक था?