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

ERC-20 কন্ট্রাক্ট ওয়াক-থ্রু

Solidity
erc-20
শিক্ষানবিস
ওরি পোমেরান্টজ
9 মার্চ, 2021
26 মিনিট পড়া

ভূমিকা

Ethereum-এর অন্যতম সাধারণ ব্যবহার হলো কোনো গোষ্ঠীর জন্য একটি ট্রেডযোগ্য টোকেন তৈরি করা, যা এক অর্থে তাদের নিজস্ব মুদ্রা। এই টোকেনগুলো সাধারণত একটি স্ট্যান্ডার্ড অনুসরণ করে, ERC-20। এই স্ট্যান্ডার্ডটি লিকুইডিটি পুল এবং ওয়ালেট-এর মতো টুল তৈরি করা সম্ভব করে, যা সমস্ত ERC-20 টোকেনের সাথে কাজ করে। এই আর্টিকেলে আমরা OpenZeppelin Solidity ERC20 ইমপ্লিমেন্টেশন (opens in a new tab) এবং সেইসাথে ইন্টারফেস ডেফিনিশন (opens in a new tab) বিশ্লেষণ করব।

এটি অ্যানোটেটেড সোর্স কোড। আপনি যদি ERC-20 ইমপ্লিমেন্ট করতে চান, তবে এই টিউটোরিয়ালটি পড়ুন (opens in a new tab)

ইন্টারফেস

ERC-20 এর মতো একটি স্ট্যান্ডার্ডের উদ্দেশ্য হলো এমন অনেক টোকেন ইমপ্লিমেন্টেশনের অনুমতি দেওয়া যা ওয়ালেট এবং ডিসেন্ট্রালাইজড এক্সচেঞ্জ-এর মতো অ্যাপ্লিকেশনগুলোর মধ্যে ইন্টারঅপারেবল। এটি অর্জন করতে, আমরা একটি ইন্টারফেস (opens in a new tab) তৈরি করি। টোকেন কন্ট্রাক্ট ব্যবহার করার প্রয়োজন এমন যেকোনো কোড ইন্টারফেসে একই ডেফিনিশন ব্যবহার করতে পারে এবং এটি ব্যবহার করে এমন সমস্ত টোকেন কন্ট্রাক্টের সাথে সামঞ্জস্যপূর্ণ হতে পারে, তা MetaMask-এর মতো কোনো ওয়ালেট হোক, etherscan.io-এর মতো কোনো ডিএ্যাপ হোক, বা লিকুইডিটি পুলের মতো ভিন্ন কোনো কন্ট্রাক্ট হোক।

Illustration of the ERC-20 interface

আপনি যদি একজন অভিজ্ঞ প্রোগ্রামার হন, তবে আপনি সম্ভবত Java (opens in a new tab) বা এমনকি C হেডার ফাইলে (opens in a new tab) একই ধরনের কনস্ট্রাক্ট দেখার কথা মনে করতে পারবেন।

এটি OpenZeppelin থেকে ERC-20 ইন্টারফেস (opens in a new tab)-এর একটি ডেফিনিশন। এটি হিউম্যান রিডেবল স্ট্যান্ডার্ড (opens in a new tab)-এর Solidity কোডে অনুবাদ। অবশ্যই, ইন্টারফেস নিজে থেকে কীভাবে কিছু করতে হবে তা নির্ধারণ করে না। এটি নিচের কন্ট্রাক্ট সোর্স কোডে ব্যাখ্যা করা হয়েছে।

 

// SPDX-License-Identifier: MIT

Solidity ফাইলে একটি লাইসেন্স আইডেন্টিফায়ার অন্তর্ভুক্ত থাকার কথা। আপনি এখানে লাইসেন্সের তালিকা দেখতে পারেন (opens in a new tab)। আপনার যদি ভিন্ন কোনো লাইসেন্সের প্রয়োজন হয়, তবে শুধু কমেন্টে তা ব্যাখ্যা করুন।

 

pragma solidity >=0.6.0 <0.8.0;

Solidity ভাষাটি এখনও দ্রুত বিকশিত হচ্ছে, এবং নতুন সংস্করণগুলো পুরোনো কোডের সাথে সামঞ্জস্যপূর্ণ নাও হতে পারে (এখানে দেখুন (opens in a new tab))। তাই, শুধুমাত্র ভাষার একটি ন্যূনতম সংস্করণ নয়, বরং একটি সর্বোচ্চ সংস্করণও নির্দিষ্ট করা ভালো, যা দিয়ে আপনি সর্বশেষ কোডটি পরীক্ষা করেছেন।

 

/* *
 * @dev EIP-তে সংজ্ঞায়িত ERC20 স্ট্যান্ডার্ডের ইন্টারফেস। */

কমেন্টে থাকা @dev হলো NatSpec ফরম্যাট (opens in a new tab)-এর অংশ, যা সোর্স কোড থেকে ডকুমেন্টেশন তৈরি করতে ব্যবহৃত হয়।

 

interface IERC20 {

প্রথা অনুযায়ী, ইন্টারফেসের নাম I দিয়ে শুরু হয়।

 

    /* *
     * @dev বর্তমানে বিদ্যমান টোকেনের পরিমাণ রিটার্ন করে। */
    function totalSupply() external view returns (uint256);

এই ফাংশনটি external, যার অর্থ এটি শুধুমাত্র কন্ট্রাক্টের বাইরে থেকে কল করা যেতে পারে (opens in a new tab)। এটি কন্ট্রাক্টে টোকেনের মোট সরবরাহ রিটার্ন করে। এই মানটি Ethereum-এর সবচেয়ে সাধারণ টাইপ, আনসাইনড 256 বিট ব্যবহার করে রিটার্ন করা হয় (256 বিট হলো ইথিরিয়াম ভার্চুয়াল মেশিন-এর নেটিভ ওয়ার্ড সাইজ)। এই ফাংশনটি একটি viewও বটে, যার অর্থ এটি স্টেট পরিবর্তন করে না, তাই ব্লকচেইন-এর প্রতিটি নোড-এ এটি চালানোর পরিবর্তে এটি একটি একক নোড-এ এক্সিকিউট করা যেতে পারে। এই ধরনের ফাংশন কোনো লেনদেন তৈরি করে না এবং এতে কোনো গ্যাস খরচ হয় না।

দ্রষ্টব্য: তাত্ত্বিকভাবে মনে হতে পারে যে কোনো কন্ট্রাক্টের নির্মাতা আসল মানের চেয়ে ছোট মোট সরবরাহ রিটার্ন করে প্রতারণা করতে পারে, যার ফলে প্রতিটি টোকেন আসলে যতটা মূল্যবান তার চেয়ে বেশি মূল্যবান বলে মনে হয়। তবে, এই ভয় ব্লকচেইন-এর আসল প্রকৃতিকে উপেক্ষা করে। ব্লকচেইন-এ যা কিছু ঘটে তা প্রতিটি নোড দ্বারা যাচাই করা যেতে পারে। এটি অর্জন করতে, প্রতিটি কন্ট্রাক্টের মেশিন ল্যাঙ্গুয়েজ কোড এবং স্টোরেজ প্রতিটি নোড-এ উপলব্ধ থাকে। যদিও আপনার কন্ট্রাক্টের জন্য Solidity কোড প্রকাশ করা বাধ্যতামূলক নয়, তবে আপনি সোর্স কোড এবং যে Solidity সংস্করণ দিয়ে এটি কম্পাইল করা হয়েছিল তা প্রকাশ না করলে কেউ আপনাকে গুরুত্ব সহকারে নেবে না, যাতে এটি আপনার দেওয়া মেশিন ল্যাঙ্গুয়েজ কোডের বিপরীতে যাচাই করা যায়। উদাহরণস্বরূপ, এই কন্ট্রাক্টটি (opens in a new tab) দেখুন।

 

    /* *
     * @dev `account`-এর মালিকানাধীন টোকেনের পরিমাণ রিটার্ন করে। */
    function balanceOf(address account) external view returns (uint256);

নাম থেকেই বোঝা যায়, balanceOf একটি একাউন্ট-এর ব্যালেন্স রিটার্ন করে। Ethereum একাউন্টগুলো Solidity-তে address টাইপ ব্যবহার করে চিহ্নিত করা হয়, যা 160 বিট ধারণ করে। এটি external এবং viewও বটে।

 

    /* *
     * @dev কলারের অ্যাকাউন্ট থেকে `recipient`-এ `amount` টোকেন স্থানান্তর করে।
     *
     * অপারেশনটি সফল হয়েছে কিনা তা নির্দেশ করে একটি বুলিয়ান মান রিটার্ন করে।
     *
     * একটি {Transfer} ইভেন্ট এমিট করে। */
    function transfer(address recipient, uint256 amount) external returns (bool);

transfer ফাংশন কলার থেকে একটি ভিন্ন এডড্রেস-এ টোকেন ট্রান্সফার করে। এর সাথে স্টেট-এর পরিবর্তন জড়িত, তাই এটি কোনো view নয়। যখন কোনো ব্যবহারকারী এই ফাংশনটি কল করে তখন এটি একটি লেনদেন তৈরি করে এবং এতে গ্যাস খরচ হয়। এটি ব্লকচেইন-এর সবাইকে ইভেন্টটি সম্পর্কে জানাতে একটি ইভেন্ট, Transfer এমিট করে।

দুটি ভিন্ন ধরনের কলারের জন্য ফাংশনটির দুই ধরনের আউটপুট রয়েছে:

  • ব্যবহারকারীরা যারা সরাসরি ইউজার ইন্টারফেস থেকে ফাংশনটি কল করে। সাধারণত ব্যবহারকারী একটি লেনদেন সাবমিট করে এবং কোনো রেসপন্সের জন্য অপেক্ষা করে না, যা অনির্দিষ্ট পরিমাণ সময় নিতে পারে। ব্যবহারকারী লেনদেন রসিদ (যা লেনদেন হ্যাস দ্বারা চিহ্নিত করা হয়) বা Transfer ইভেন্টটি দেখে কী ঘটেছে তা দেখতে পারে।
  • অন্যান্য কন্ট্রাক্ট, যা সামগ্রিক লেনদেন-এর অংশ হিসেবে ফাংশনটি কল করে। সেই কন্ট্রাক্টগুলো অবিলম্বে ফলাফল পায়, কারণ তারা একই লেনদেন-এ চলে, তাই তারা ফাংশন রিটার্ন ভ্যালু ব্যবহার করতে পারে।

কন্ট্রাক্টের স্টেট পরিবর্তন করে এমন অন্যান্য ফাংশন দ্বারা একই ধরনের আউটপুট তৈরি করা হয়।

 

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

    /* *
     * @dev `spender` {transferFrom}-এর মাধ্যমে `owner`-এর পক্ষে যে পরিমাণ টোকেন খরচ করার অনুমতি পাবে তার অবশিষ্ট সংখ্যা রিটার্ন করে। এটি ডিফল্টরূপে শূন্য থাকে।
     *
     * {approve} বা {transferFrom} কল করা হলে এই মানটি পরিবর্তিত হয়। */
    function allowance(address owner, address spender) external view returns (uint256);

allowance ফাংশন যে কাউকে কোয়েরি করে দেখতে দেয় যে একটি এডড্রেস (owner) অন্য একটি এডড্রেস (spender)-কে কী পরিমাণ অ্যালাউন্স খরচ করতে দেয়।

 

approve ফাংশন একটি অ্যালাউন্স তৈরি করে। এটি কীভাবে অপব্যবহার করা যেতে পারে সে সম্পর্কে মেসেজটি পড়তে ভুলবেন না। Ethereum-এ আপনি আপনার নিজের লেনদেন-এর ক্রম নিয়ন্ত্রণ করেন, কিন্তু আপনি অন্য লোকেদের লেনদেন কোন ক্রমে এক্সিকিউট করা হবে তা নিয়ন্ত্রণ করতে পারবেন না, যদি না আপনি অন্য পক্ষের লেনদেন সম্পন্ন হতে দেখার আগে আপনার নিজের লেনদেন সাবমিট না করেন।

 

    /* *
     * @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)। এটি যেমন আছে তেমন ব্যবহার করার উদ্দেশ্যে নয়, তবে আপনি এটিকে ব্যবহারযোগ্য কিছুতে প্রসারিত করতে এটি থেকে ইনহেরিট (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)-এর মতোই আচরণ করে, তবে পার্থক্য হলো কি (key) গুলো নিউমেরিক ভ্যালু। স্টোরেজ শুধুমাত্র সেই এন্ট্রিগুলোর জন্য বরাদ্দ করা হয় যেগুলোর মান ডিফল্ট (শূন্য) থেকে আলাদা।

    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 নয়।

একদিকে, Ethereum-এ ফ্লোটিং পয়েন্ট বা ফ্র্যাকশনাল ভেরিয়েবল নেই। অন্যদিকে, মানুষ টোকেন ভাগ করতে সক্ষম হতে পছন্দ করে। মুদ্রার জন্য মানুষ সোনা বেছে নেওয়ার একটি কারণ ছিল যে যখন কেউ একটি হাঁসের মূল্যের গরু কিনতে চাইত তখন খুচরা করা কঠিন ছিল।

এর সমাধান হলো ইন্টিজারগুলোর ট্র্যাক রাখা, তবে আসল টোকেনের পরিবর্তে একটি ফ্র্যাকশনাল টোকেন গণনা করা যা প্রায় মূল্যহীন। ইথারের ক্ষেত্রে, ফ্র্যাকশনাল টোকেনটিকে wei বলা হয় এবং 10^18 wei হলো এক ETH-এর সমান। লেখার সময়, 10,000,000,000,000 wei হলো প্রায় এক ইউএস বা ইউরো সেন্ট।

অ্যাপ্লিকেশনগুলোকে জানতে হবে কীভাবে টোকেন ব্যালেন্স প্রদর্শন করতে হয়। যদি কোনো ব্যবহারকারীর 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, যার অর্থ মেমোরিতে স্টোর করা একটি স্ট্রিং রিটার্ন করা। ভেরিয়েবলগুলো, যেমন স্ট্রিং, তিনটি লোকেশনে স্টোর করা যেতে পারে:

লাইফটাইমকন্ট্রাক্ট অ্যাক্সেসগ্যাস খরচ
Memoryফাংশন কলরিড/রাইটদশ বা শত (উচ্চতর লোকেশনের জন্য বেশি)
Calldataফাংশন কলশুধুমাত্র রিডরিটার্ন টাইপ হিসেবে ব্যবহার করা যাবে না, শুধুমাত্র ফাংশন প্যারামিটার টাইপ
Storageপরিবর্তন না হওয়া পর্যন্তরিড/রাইটবেশি (রিডের জন্য 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];
    }

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

টোকেন ট্রান্সফার

    /* *
     * @dev {IERC20-transfer} দেখুন।
     *
     * প্রয়োজনীয়তা:
     *
     * - `recipient` জিরো অ্যাড্রেস হতে পারে না।
     * - কলারের ব্যালেন্স কমপক্ষে `amount` হতে হবে। */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {

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। উপরন্তু, OpenZeppelin ইমপ্লিমেন্টেশন বেসিক স্ট্যান্ডার্ডের বাইরে গিয়ে কিছু ফিচার অন্তর্ভুক্ত করে যা নিরাপত্তা উন্নত করে: 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;
    }

OpenZeppelin নিরাপত্তা সংযোজন

একটি নন-জিরো অ্যালাউন্সকে অন্য একটি নন-জিরো মানে সেট করা বিপজ্জনক, কারণ আপনি শুধুমাত্র আপনার নিজের লেনদেন-এর ক্রম নিয়ন্ত্রণ করেন, অন্য কারও নয়। কল্পনা করুন আপনার দুজন ব্যবহারকারী আছে, অ্যালিস যে সহজ-সরল এবং বিল যে অসৎ। অ্যালিস বিলের কাছ থেকে কিছু পরিষেবা চায়, যার দাম সে মনে করে পাঁচটি টোকেন - তাই সে বিলকে পাঁচটি টোকেনের একটি অ্যালাউন্স দেয়।

তারপর কিছু পরিবর্তন হয় এবং বিলের দাম বেড়ে দশটি টোকেন হয়। অ্যালিস, যে এখনও পরিষেবাটি চায়, একটি লেনদেন পাঠায় যা বিলের অ্যালাউন্স দশে সেট করে। বিল যখন লেনদেন পুলে এই নতুন লেনদেনটি দেখে তখন সে একটি লেনদেন পাঠায় যা অ্যালিসের পাঁচটি টোকেন খরচ করে এবং এর গ্যাস প্রাইস অনেক বেশি থাকে যাতে এটি দ্রুত মাইন করা যায়। এইভাবে বিল প্রথমে পাঁচটি টোকেন খরচ করতে পারে এবং তারপর, অ্যালিসের নতুন অ্যালাউন্স মাইন হওয়ার পর, আরও দশটি খরচ করতে পারে মোট পনেরোটি টোকেনের জন্য, যা অ্যালিস অনুমোদন করতে চেয়েছিল তার চেয়ে বেশি। এই কৌশলটিকে ফ্রন্ট-রানিং (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 এটি সাধারণ যোগের মতো র‍্যাপ অ্যারাউন্ড করে না।

টোকেন তথ্য পরিবর্তনকারী ফাংশন

এই চারটি ফাংশন আসল কাজ করে: _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");

Ethereum-এ আসলে কেউ জিরো এডড্রেস-এর মালিক নয় (অর্থাৎ, কেউ এমন কোনো প্রাইভেট কি জানে না যার ম্যাচিং পাবলিক কি জিরো এডড্রেস-এ রূপান্তরিত হয়)। যখন লোকেরা সেই এডড্রেস ব্যবহার করে, তখন এটি সাধারণত একটি সফটওয়্যার বাগ হয় - তাই জিরো এডড্রেস প্রেরক বা প্রাপক হিসেবে ব্যবহৃত হলে আমরা ব্যর্থ হই।

 

        _beforeTokenTransfer(sender, recipient, amount);

এই কন্ট্রাক্টটি ব্যবহার করার দুটি উপায় রয়েছে:

  1. আপনার নিজের কোডের জন্য এটি একটি টেমপ্লেট হিসেবে ব্যবহার করুন
  2. এটি থেকে ইনহেরিট করুন (opens in a new tab), এবং শুধুমাত্র সেই ফাংশনগুলো ওভাররাইড করুন যা আপনার পরিবর্তন করা প্রয়োজন

দ্বিতীয় পদ্ধতিটি অনেক ভালো কারণ OpenZeppelin 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);

এই লাইনগুলোই আসলে ট্রান্সফার করে। মনে রাখবেন যে এগুলোর মধ্যে কিছুই নেই এবং আমরা প্রাপকের সাথে যোগ করার আগে প্রেরকের কাছ থেকে ট্রান্সফার করা পরিমাণ বিয়োগ করি। এটি গুরুত্বপূর্ণ কারণ যদি মাঝখানে অন্য কোনো কন্ট্রাক্টে কল করা হতো, তবে এটি এই কন্ট্রাক্টকে প্রতারণা করতে ব্যবহার করা যেতে পারত। এইভাবে ট্রান্সফারটি অ্যাটমিক হয়, এর মাঝখানে কিছুই ঘটতে পারে না।

 

        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-এ দেখতে পাচ্ছেন)। এর কারণ হলো স্টেট পরিবর্তনের সময় আপনার একটি অসামঞ্জস্যপূর্ণ স্টেট থাকে। উদাহরণস্বরূপ, আপনি প্রেরকের ব্যালেন্স থেকে যে সময় বিয়োগ করেন এবং প্রাপকের ব্যালেন্সে যে সময় যোগ করেন তার মধ্যে বাস্তবে যতটা টোকেন থাকা উচিত তার চেয়ে কম টোকেন থাকে। যদি এগুলোর মধ্যে কোনো অপারেশন থাকে, বিশেষ করে অন্য কোনো কন্ট্রাক্টে কল করা হয়, তবে এটি সম্ভাব্যভাবে অপব্যবহার করা যেতে পারে।

এখন যেহেতু আপনি দেখেছেন কীভাবে OpenZeppelin ERC-20 কন্ট্রাক্ট লেখা হয় এবং বিশেষ করে কীভাবে এটি আরও নিরাপদ করা হয়, যান এবং আপনার নিজের নিরাপদ কন্ট্রাক্ট এবং অ্যাপ্লিকেশনগুলো লিখুন।

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

পেজ সর্বশেষ আপডেট করা হয়েছে: 15 এপ্রিল, 2026

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