মূল কন্টেন্টে যান

সেফটি রেইলস সহ ERC-20

erc-20
শিক্ষানবিস
ওরি পোমেরান্টজ
15 আগস্ট, 2022
8 মিনিট পড়া

ভূমিকা

ইথিরিয়ামের অন্যতম সেরা দিক হলো এখানে এমন কোনো কেন্দ্রীয় কর্তৃপক্ষ নেই যা আপনার লেনদেন পরিবর্তন বা বাতিল করতে পারে। আবার ইথিরিয়ামের অন্যতম বড় সমস্যা হলো এখানে এমন কোনো কেন্দ্রীয় কর্তৃপক্ষ নেই যার ব্যবহারকারীর ভুল বা অবৈধ লেনদেন বাতিল করার ক্ষমতা আছে। এই আর্টিকেলে আপনি ERC-20 টোকেন নিয়ে ব্যবহারকারীদের করা কিছু সাধারণ ভুল সম্পর্কে জানবেন, পাশাপাশি কীভাবে এমন ERC-20 কন্ট্রাক্ট তৈরি করতে হয় যা ব্যবহারকারীদের এই ভুলগুলো এড়াতে সাহায্য করে, অথবা কোনো কেন্দ্রীয় কর্তৃপক্ষকে কিছু ক্ষমতা দেয় (উদাহরণস্বরূপ একাউন্ট ফ্রিজ করা) তা শিখবেন।

মনে রাখবেন, যদিও আমরা OpenZeppelin ERC-20 token contract (opens in a new tab) ব্যবহার করব, এই আর্টিকেলে এটি বিস্তারিতভাবে ব্যাখ্যা করা হয়নি। আপনি এই তথ্য এখানে পেতে পারেন।

আপনি যদি সম্পূর্ণ সোর্স কোড দেখতে চান:

  1. Remix IDE (opens in a new tab) খুলুন।
  2. ক্লোন গিটহাব আইকনে ক্লিক করুন (clone github icon)।
  3. গিটহাব রিপোজিটরি https://github.com/qbzzt/20220815-erc20-safety-rails ক্লোন করুন।
  4. contracts > erc20-safety-rails.sol খুলুন।

একটি ERC-20 কন্ট্রাক্ট তৈরি করা

সেফটি রেইল কার্যকারিতা যোগ করার আগে আমাদের একটি ERC-20 কন্ট্রাক্ট প্রয়োজন। এই আর্টিকেলে আমরা the OpenZeppelin Contracts Wizard (opens in a new tab) ব্যবহার করব। এটি অন্য একটি ব্রাউজারে খুলুন এবং এই নির্দেশাবলী অনুসরণ করুন:

  1. ERC20 নির্বাচন করুন।

  2. এই সেটিংসগুলো লিখুন:

    ParameterValue
    NameSafetyRailsToken
    SymbolSAFE
    Premint1000
    FeaturesNone
    Access ControlOwnable
    UpgradabilityNone
  3. উপরে স্ক্রোল করুন এবং Open in Remix (Remix-এর জন্য) বা অন্য কোনো পরিবেশ ব্যবহার করতে Download-এ ক্লিক করুন। আমি ধরে নিচ্ছি আপনি Remix ব্যবহার করছেন, যদি আপনি অন্য কিছু ব্যবহার করেন তবে শুধু উপযুক্ত পরিবর্তনগুলো করে নিন।

  4. এখন আমাদের কাছে একটি সম্পূর্ণ কার্যকরী ERC-20 কন্ট্রাক্ট আছে। ইমপোর্ট করা কোড দেখতে আপনি .deps > npm প্রসারিত করতে পারেন।

  5. কন্ট্রাক্টটি কম্পাইল, ডিপ্লয় করুন এবং এটি একটি ERC-20 কন্ট্রাক্ট হিসেবে কাজ করে কিনা তা দেখতে এর সাথে কাজ করুন। আপনার যদি Remix কীভাবে ব্যবহার করতে হয় তা শেখার প্রয়োজন হয়, তবে এই টিউটোরিয়ালটি ব্যবহার করুন (opens in a new tab)

সাধারণ ভুলগুলো

ভুলগুলো

ব্যবহারকারীরা মাঝে মাঝে ভুল এডড্রেস-এ টোকেন পাঠিয়ে দেন। যদিও তারা কী করতে চেয়েছিলেন তা জানার জন্য আমরা তাদের মন পড়তে পারি না, তবে দুটি ধরনের ভুল আছে যা অনেক বেশি ঘটে এবং সহজেই শনাক্ত করা যায়:

  1. কন্ট্রাক্টের নিজস্ব এডড্রেস-এ টোকেন পাঠানো। উদাহরণস্বরূপ, Optimism's OP token (opens in a new tab) দুই মাসেরও কম সময়ে 120,000-এর বেশি (opens in a new tab) OP টোকেন জমা করতে সক্ষম হয়েছিল। এটি একটি উল্লেখযোগ্য পরিমাণ সম্পদের প্রতিনিধিত্ব করে যা সম্ভবত মানুষ হারিয়েছে।

  2. একটি খালি এডড্রেস-এ টোকেন পাঠানো, যা কোনো এক্সটার্নালি ওনড একাউন্ট বা স্মার্ট কন্ট্রাক্ট-এর সাথে মিলে না। যদিও এটি কত ঘন ঘন ঘটে তার কোনো পরিসংখ্যান আমার কাছে নেই, তবে একটি ঘটনায় 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 virtual
3 override(ERC20)
4 {
5 super._beforeTokenTransfer(from, to, amount);
6 }

আপনি যদি Solidity-এর সাথে খুব বেশি পরিচিত না হন তবে এই ফাংশনের কিছু অংশ নতুন মনে হতে পারে:

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) বলা হয়, তবে এটি সরাসরি Solidity-তে পাওয়া যায় না। এর জন্য আমাদের Yul (opens in a new tab) ব্যবহার করতে হবে, যা হলো EVM অ্যাসেম্বলি। Solidity থেকে আমরা অন্যান্য মান ব্যবহার করতে পারি (<address>.code এবং <address>.codehash (opens in a new tab)), তবে সেগুলোর খরচ বেশি।

চলুন নতুন কোডটি লাইন বাই লাইন দেখে নিই:

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

এটি হলো প্রথম প্রয়োজনীয়তা, চেক করুন যে 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, "Can't send tokens to an empty address");

এবং সবশেষে, আমাদের কাছে খালি এডড্রেস-এর জন্য আসল চেকটি রয়েছে।

অ্যাডমিনিস্ট্রেটিভ অ্যাক্সেস

মাঝে মাঝে এমন একজন অ্যাডমিনিস্ট্রেটর থাকা দরকারী যিনি ভুলগুলো বাতিল করতে পারেন। অপব্যবহারের সম্ভাবনা কমাতে, এই অ্যাডমিনিস্ট্রেটর একটি মাল্টিসিগ (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 টোকেন পাঠায়, যা সেগুলো বের করার একটি উপায় চাওয়ার আরেকটি কারণ।

OpenZeppelin অ্যাডমিনিস্ট্রেটিভ অ্যাক্সেস সক্ষম করার জন্য দুটি মেকানিজম প্রদান করে:

সরলতার খাতিরে, এই আর্টিকেলে আমরা Ownable ব্যবহার করব।

কন্ট্রাক্ট ফ্রিজ এবং থয়িং (thawing) করা

কন্ট্রাক্ট ফ্রিজ এবং থয়িং করার জন্য বেশ কয়েকটি পরিবর্তনের প্রয়োজন:

  • কোন এডড্রেসগুলো ফ্রিজ করা হয়েছে তার ট্র্যাক রাখতে এডড্রেস থেকে বুলিয়ান (opens in a new tab)-এ একটি ম্যাপিং (opens in a new tab)। সমস্ত মান প্রাথমিকভাবে শূন্য থাকে, যা বুলিয়ান মানের জন্য ফলস (false) হিসেবে ব্যাখ্যা করা হয়। আমরা এটাই চাই কারণ ডিফল্টরূপে একাউন্টগুলো ফ্রিজ করা থাকে না।

    1 mapping(address => bool) public frozenAccounts;
  • যখন কোনো একাউন্ট ফ্রিজ বা থয়িং করা হয় তখন আগ্রহী যে কাউকে জানানোর জন্য ইভেন্ট (opens in a new tab)। প্রযুক্তিগতভাবে বলতে গেলে এই কাজগুলোর জন্য ইভেন্টের প্রয়োজন নেই, তবে এটি অফচেইন কোডকে এই ইভেন্টগুলো শুনতে এবং কী ঘটছে তা জানতে সাহায্য করে। যখন অন্য কারো জন্য প্রাসঙ্গিক হতে পারে এমন কিছু ঘটে তখন একটি স্মার্ট কন্ট্রাক্ট-এর জন্য এগুলো এমিট (emit) করা ভালো শিষ্টাচার হিসেবে বিবেচিত হয়।

    ইভেন্টগুলো ইনডেক্স করা থাকে তাই কোনো একাউন্ট কতবার ফ্রিজ বা থয়িং করা হয়েছে তা অনুসন্ধান করা সম্ভব হবে।

    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], "Account already frozen");
    3 frozenAccounts[addr] = true;
    4 emit AccountFrozen(addr);
    5 } // freezeAccount

    যদি একাউন্টটি ইতিমধ্যে ফ্রিজ করা থাকে, তবে রিভার্ট করুন। অন্যথায়, এটি ফ্রিজ করুন এবং একটি ইভেন্ট emit করুন।

  • একটি ফ্রিজ করা একাউন্ট থেকে অর্থ সরানো রোধ করতে _beforeTokenTransfer পরিবর্তন করুন। মনে রাখবেন যে ফ্রিজ করা একাউন্টে এখনও অর্থ ট্রান্সফার করা যেতে পারে।

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

অ্যাসেট ক্লিনআপ

এই কন্ট্রাক্ট দ্বারা ধারণ করা 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) অন্তর্ভুক্ত রয়েছে, যা একটি OpenZeppelin ERC-20 কন্ট্রাক্টের ইন্টারফেস।

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

এটি একটি ক্লিনআপ ফাংশন, তাই সম্ভবত আমরা কোনো টোকেন রেখে যেতে চাই না। ব্যবহারকারীর কাছ থেকে ম্যানুয়ালি ব্যালেন্স নেওয়ার পরিবর্তে, আমরা প্রক্রিয়াটি স্বয়ংক্রিয় করতে পারি।

উপসংহার

এটি কোনো নিখুঁত সমাধান নয় - "ব্যবহারকারী ভুল করেছেন" সমস্যার কোনো নিখুঁত সমাধান নেই। তবে, এই ধরনের চেকগুলো ব্যবহার করে অন্তত কিছু ভুল প্রতিরোধ করা যেতে পারে। একাউন্ট ফ্রিজ করার ক্ষমতা, যদিও বিপজ্জনক, হ্যাকারকে চুরি করা তহবিল থেকে বঞ্চিত করে নির্দিষ্ট হ্যাকের ক্ষতি সীমিত করতে ব্যবহার করা যেতে পারে।

আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)

পেজ সর্বশেষ আপডেট: 3 মার্চ, 2026

এই টিউটোরিয়ালটি কি সহায়ক ছিল?