مرکزی مواد پر جائیں

⁦ERC-20⁩ حفاظتی ریلز کے ساتھ

erc-20
ابتدائی
اوری پومرانٹز
۱۵ اگست، ۲۰۲۲
11 منٹ کا مطالعہ

تعارف

ایتھیریم کی ایک بہترین بات یہ ہے کہ کوئی ایسی مرکزی اتھارٹی نہیں ہے جو آپ کی ٹرانزیکشنز کو تبدیل یا منسوخ کر سکے۔ ایتھیریم کے ساتھ ایک بڑا مسئلہ یہ بھی ہے کہ کوئی ایسی مرکزی اتھارٹی نہیں ہے جس کے پاس صارفین کی غلطیوں یا غیر قانونی ٹرانزیکشنز کو منسوخ کرنے کا اختیار ہو۔ اس مضمون میں آپ ان عام غلطیوں کے بارے میں جانیں گے جو صارفین ERC-20 ٹوکنز کے ساتھ کرتے ہیں، اور ساتھ ہی یہ بھی سیکھیں گے کہ ایسے ERC-20 کنٹریکٹس کیسے بنائے جائیں جو صارفین کو ان غلطیوں سے بچنے میں مدد دیں، یا جو کسی مرکزی اتھارٹی کو کچھ اختیار دیں (مثال کے طور پر اکاؤنٹس کو منجمد کرنے کا)۔

نوٹ کریں کہ اگرچہ ہم اوپن زیپلن ERC-20 ٹوکن کنٹریکٹ (opens in a new tab) استعمال کریں گے، لیکن یہ مضمون اس کی تفصیل سے وضاحت نہیں کرتا۔ آپ یہ معلومات یہاں حاصل کر سکتے ہیں۔

اگر آپ مکمل سورس کوڈ دیکھنا چاہتے ہیں:

  1. Remix 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 کنٹریکٹ کی ضرورت ہے۔ اس مضمون میں ہم اوپن زیپلن کنٹریکٹس وزرڈ (opens in a new tab) استعمال کریں گے۔ اسے کسی دوسرے براؤزر میں کھولیں اور ان ہدایات پر عمل کریں:

  1. ERC20 منتخب کریں۔

  2. یہ ترتیبات درج کریں:

    پیرامیٹرقدر
    نامSafetyRailsToken
    علامتSAFE
    پری منٹ1000
    خصوصیاتکوئی نہیں
    ایکسیس کنٹرولOwnable
    اپ گریڈ ایبلٹیکوئی نہیں
  3. اوپر سکرول کریں اور Remix کے لیے Open in Remix پر کلک کریں یا کسی مختلف ماحول کو استعمال کرنے کے لیے Download پر کلک کریں۔ میں فرض کر رہا ہوں کہ آپ Remix استعمال کر رہے ہیں، اگر آپ کچھ اور استعمال کرتے ہیں تو بس مناسب تبدیلیاں کر لیں۔

  4. اب ہمارے پاس ایک مکمل فعال ERC-20 کنٹریکٹ ہے۔ آپ امپورٹ شدہ کوڈ دیکھنے کے لیے .deps > npm کو پھیلا سکتے ہیں۔

  5. کنٹریکٹ کو مرتب کریں، تعینات کریں، اور اس کے ساتھ کھیل کر دیکھیں کہ یہ ایک ERC-20 کنٹریکٹ کے طور پر کام کرتا ہے۔ اگر آپ کو Remix استعمال کرنے کا طریقہ سیکھنے کی ضرورت ہے، تو یہ ٹیوٹوریل استعمال کریں (opens in a new tab)۔

عام غلطیاں

غلطیاں

صارفین بعض اوقات غلط پتہ پر ٹوکن بھیج دیتے ہیں۔ اگرچہ ہم ان کے ذہن نہیں پڑھ سکتے کہ وہ کیا کرنا چاہتے تھے، لیکن دو قسم کی غلطیاں ایسی ہیں جو بہت زیادہ ہوتی ہیں اور ان کا پتہ لگانا آسان ہے:

  1. ٹوکنز کو کنٹریکٹ کے اپنے پتہ پر بھیجنا۔ مثال کے طور پر، آپٹیمزم کے OP ٹوکن (opens in a new tab) نے دو ماہ سے بھی کم عرصے میں 120,000 سے زیادہ (opens in a new tab) OP ٹوکنز جمع کر لیے۔ یہ دولت کی ایک نمایاں مقدار کی نمائندگی کرتا ہے جو غالباً لوگوں نے کھو دی ہے۔

  2. ٹوکنز کو کسی خالی پتہ پر بھیجنا، جو کسی بیرونی ملکیت والے اکاؤنٹ (EOA) یا سمارٹ کنٹریکٹ سے مطابقت نہیں رکھتا۔ اگرچہ میرے پاس اس کے اعداد و شمار نہیں ہیں کہ ایسا کتنی بار ہوتا ہے، لیکن ایک واقعے میں 20,000,000 ٹوکنز کا نقصان ہو سکتا تھا (opens in a new tab)۔

منتقلی کو روکنا

اوپن زیپلن ERC-20 کنٹریکٹ میں ایک ہک، _beforeTokenTransfer (opens in a new tab) شامل ہے، جسے ٹوکن کی منتقلی سے پہلے کال کیا جاتا ہے۔ پہلے سے طے شدہ طور پر یہ ہک کچھ نہیں کرتا، لیکن ہم اس پر اپنی فعالیت شامل کر سکتے ہیں، جیسے کہ ایسے چیکس جو کوئی مسئلہ ہونے پر ریورٹ کر دیں۔

ہک استعمال کرنے کے لیے، کنسٹرکٹر کے بعد یہ فنکشن شامل کریں:

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal virtual
        override(ERC20)
    {
        super._beforeTokenTransfer(from, to, amount);
    }

اگر آپ Solidity سے زیادہ واقف نہیں ہیں تو اس فنکشن کے کچھ حصے آپ کے لیے نئے ہو سکتے ہیں:

        internal virtual

virtual کلیدی لفظ کا مطلب یہ ہے کہ جس طرح ہم نے ERC20 سے فعالیت وراثت میں لی اور اس فنکشن کو اوور رائیڈ کیا، اسی طرح دوسرے کنٹریکٹس ہم سے وراثت لے سکتے ہیں اور اس فنکشن کو اوور رائیڈ کر سکتے ہیں۔

        override(ERC20)

ہمیں واضح طور پر بتانا ہوگا کہ ہم _beforeTokenTransfer کی ERC20 ٹوکن تعریف کو اوور رائیڈ (opens in a new tab) کر رہے ہیں۔ عام طور پر، سیکیورٹی کے نقطہ نظر سے، واضح تعریفیں غیر واضح تعریفوں سے بہت بہتر ہوتی ہیں - اگر کوئی چیز آپ کے بالکل سامنے ہو تو آپ یہ نہیں بھول سکتے کہ آپ نے کچھ کیا ہے۔ یہی وجہ ہے کہ ہمیں یہ بتانے کی ضرورت ہے کہ ہم کس سپر کلاس کے _beforeTokenTransfer کو اوور رائیڈ کر رہے ہیں۔

        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))، لیکن ان کی لاگت زیادہ ہوتی ہے۔

آئیے نئے کوڈ کا لائن بہ لائن جائزہ لیتے ہیں:

        require(to != address(this), "Can't send tokens to the contract address");

یہ پہلی ضرورت ہے، چیک کریں کہ to اور this(address) ایک ہی چیز نہیں ہیں۔

        bool isToContract;
        assembly {
           isToContract := gt(extcodesize(to), 0)
        }

اس طرح ہم چیک کرتے ہیں کہ آیا کوئی پتہ کنٹریکٹ ہے۔ ہم براہ راست Yul سے آؤٹ پٹ حاصل نہیں کر سکتے، اس لیے اس کے بجائے ہم نتیجہ کو محفوظ کرنے کے لیے ایک متغیر کی تعریف کرتے ہیں (اس صورت میں isToContract)۔ Yul کے کام کرنے کا طریقہ یہ ہے کہ ہر آپ کوڈ کو ایک فنکشن سمجھا جاتا ہے۔ لہذا پہلے ہم کنٹریکٹ کا سائز حاصل کرنے کے لیے EXTCODESIZE (opens in a new tab) کو کال کرتے ہیں، اور پھر یہ چیک کرنے کے لیے GT (opens in a new tab) استعمال کرتے ہیں کہ یہ صفر نہیں ہے (ہم غیر علامتی اعداد (unsigned integers) کے ساتھ کام کر رہے ہیں، لہذا یقیناً یہ منفی نہیں ہو سکتا)۔ پھر ہم نتیجہ کو isToContract میں لکھتے ہیں۔

        require(to.balance != 0 || isToContract, "Can't send tokens to an empty address");

اور آخر کار، ہمارے پاس خالی پتوں کے لیے اصل چیک موجود ہے۔

انتظامی رسائی

بعض اوقات ایک ایسا منتظم ہونا مفید ہوتا ہے جو غلطیوں کو منسوخ کر سکے۔ غلط استعمال کے امکانات کو کم کرنے کے لیے، یہ منتظم ایک ملٹی سگ (opens in a new tab) ہو سکتا ہے تاکہ متعدد لوگوں کو کسی کارروائی پر متفق ہونا پڑے۔ اس مضمون میں ہمارے پاس دو انتظامی خصوصیات ہوں گی:

  1. اکاؤنٹس کو منجمد اور غیر منجمد کرنا۔ یہ اس وقت مفید ہو سکتا ہے، مثال کے طور پر، جب کسی اکاؤنٹ سے سمجھوتہ (compromise) ہو گیا ہو۔

  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 استعمال کرتے ہیں۔

کنٹریکٹس کو منجمد اور بحال کرنا

کنٹریکٹس کو منجمد اور بحال کرنے کے لیے کئی تبدیلیوں کی ضرورت ہوتی ہے:

  • پتوں سے بولینز (booleans) (opens in a new tab) تک ایک میپنگ (opens in a new tab) تاکہ یہ ٹریک رکھا جا سکے کہ کون سے پتے منجمد ہیں۔ تمام قدریں ابتدائی طور پر صفر ہوتی ہیں، جسے بولین قدروں کے لیے غلط (false) سمجھا جاتا ہے۔ ہم یہی چاہتے ہیں کیونکہ پہلے سے طے شدہ طور پر اکاؤنٹس منجمد نہیں ہوتے ہیں۔

        mapping(address => bool) public frozenAccounts;
    
  • ایونٹس (opens in a new tab) تاکہ کسی بھی دلچسپی رکھنے والے کو مطلع کیا جا سکے جب کوئی اکاؤنٹ منجمد یا بحال ہو۔ تکنیکی طور پر ان کارروائیوں کے لیے ایونٹس کی ضرورت نہیں ہوتی، لیکن یہ آف چین کوڈ کو ان ایونٹس کو سننے اور یہ جاننے میں مدد کرتا ہے کہ کیا ہو رہا ہے۔ سمارٹ کنٹریکٹ کے لیے یہ اچھے آداب سمجھے جاتے ہیں کہ جب کوئی ایسی چیز ہو جو کسی اور سے متعلق ہو سکتی ہے تو وہ انہیں خارج (emit) کرے۔

    ایونٹس کو انڈیکس کیا جاتا ہے تاکہ ان تمام اوقات کو تلاش کرنا ممکن ہو سکے جب کوئی اکاؤنٹ منجمد یا بحال کیا گیا ہو۔

      // جب اکاؤنٹس کو منجمد یا غیر منجمد کیا جاتا ہے
      event AccountFrozen(address indexed _addr);
      event AccountThawed(address indexed _addr);
    
  • اکاؤنٹس کو منجمد اور بحال کرنے کے فنکشنز۔ یہ دونوں فنکشنز تقریباً ایک جیسے ہیں، اس لیے ہم صرف فریز (freeze) فنکشن کا جائزہ لیں گے۔

        function freezeAccount(address addr)
          public
          onlyOwner
    

    public (opens in a new tab) کے طور پر نشان زد فنکشنز کو دوسرے سمارٹ کنٹریکٹس سے یا براہ راست کسی ٹرانزیکشن کے ذریعے کال کیا جا سکتا ہے۔

      {
          require(!frozenAccounts[addr], "Account already frozen");
          frozenAccounts[addr] = true;
          emit AccountFrozen(addr);
      }  // freezeAccount
    

    اگر اکاؤنٹ پہلے ہی منجمد ہے، تو ریورٹ کریں۔ بصورت دیگر، اسے منجمد کریں اور ایک ایونٹ emit کریں۔

  • کسی منجمد اکاؤنٹ سے رقم کی منتقلی کو روکنے کے لیے _beforeTokenTransfer کو تبدیل کریں۔ نوٹ کریں کہ رقم اب بھی منجمد اکاؤنٹ میں منتقل کی جا سکتی ہے۔

         require(!frozenAccounts[from], "The account is frozen");
    

اثاثوں کی صفائی

اس کنٹریکٹ کے پاس موجود ERC-20 ٹوکنز کو جاری کرنے کے لیے ہمیں اس ٹوکن کنٹریکٹ پر ایک فنکشن کال کرنے کی ضرورت ہے جس سے وہ تعلق رکھتے ہیں، یا تو transfer (opens in a new tab) یا approve (opens in a new tab)۔ اس صورت میں الاؤنسز پر گیس ضائع کرنے کا کوئی فائدہ نہیں، ہم براہ راست منتقلی بھی کر سکتے ہیں۔

    function cleanupERC20(
        address erc20,
        address dest
    )
        public
        onlyOwner
    {
        IERC20 token = IERC20(erc20);

جب ہمیں پتہ موصول ہوتا ہے تو کنٹریکٹ کے لیے ایک آبجیکٹ بنانے کا یہ نحو (syntax) ہے۔ ہم ایسا کر سکتے ہیں کیونکہ ہمارے پاس سورس کوڈ کے حصے کے طور پر ERC20 ٹوکنز کی تعریف موجود ہے (لائن 4 دیکھیں)، اور اس فائل میں IERC20 کی تعریف (opens in a new tab) شامل ہے، جو کہ اوپن زیپلن ERC-20 کنٹریکٹ کا انٹرفیس ہے۔

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

یہ ایک کلین اپ فنکشن ہے، لہذا غالباً ہم کوئی ٹوکن نہیں چھوڑنا چاہتے۔ صارف سے دستی طور پر بیلنس حاصل کرنے کے بجائے، ہم اس عمل کو خودکار بھی بنا سکتے ہیں۔

نتیجہ

یہ کوئی کامل حل نہیں ہے - "صارف نے غلطی کی" کے مسئلے کا کوئی کامل حل نہیں ہے۔ تاہم، اس قسم کے چیکس کا استعمال کم از کم کچھ غلطیوں کو روک سکتا ہے۔ اکاؤنٹس کو منجمد کرنے کی صلاحیت، اگرچہ خطرناک ہے، ہیکر کو چوری شدہ فنڈز سے انکار کر کے بعض ہیکس کے نقصان کو محدود کرنے کے لیے استعمال کی جا سکتی ہے۔

میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔