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

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

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

ভূমিকা

ইথেরিয়াম-এর অন্যতম সাধারণ ব্যবহার হলো কোনো গোষ্ঠীর জন্য একটি ট্রেডযোগ্য টোকেন তৈরি করা, যা এক অর্থে তাদের নিজস্ব মুদ্রা। এই টোকেনগুলো সাধারণত একটি স্ট্যান্ডার্ড অনুসরণ করে, ERC-20। এই স্ট্যান্ডার্ডটি এমন টুল তৈরি করা সম্ভব করে, যেমন তারল্য পুল এবং ওয়ালেট, যা সমস্ত ERC-20 টোকেনের সাথে কাজ করে। এই নিবন্ধে আমরা ওপেনজেপেলিন 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) তৈরি করি। টোকেন কন্ট্রাক্ট ব্যবহার করার প্রয়োজন এমন যেকোনো কোড ইন্টারফেসে একই ডেফিনিশন ব্যবহার করতে পারে এবং এটি ব্যবহার করে এমন সমস্ত টোকেন কন্ট্রাক্টের সাথে সামঞ্জস্যপূর্ণ হতে পারে, তা মেটামাস্ক-এর মতো ওয়ালেট হোক, etherscan.io-এর মতো একটি বিকেন্দ্রীকৃত অ্যাপ্লিকেশন (dapp) হোক, বা তারল্য পুল-এর মতো ভিন্ন কোনো কন্ট্রাক্ট হোক।

Illustration of the ERC-20 interface

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

এটি ওপেনজেপেলিন থেকে 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-তে সংজ্ঞায়িত ERC-20 স্ট্যান্ডার্ডের ইন্টারফেস।
 */

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

 

interface IERC20 {

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

 

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

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

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

 

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

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

 

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

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

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

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

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

 

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

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

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

 

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

 

    /**
     * @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), যা <0.8.0 Solidity সংস্করণগুলোর জন্য গাণিতিক ওভারফ্লো/আন্ডারফ্লো প্রতিরোধ করে। ≥0.8.0 Solidity-তে, গাণিতিক ক্রিয়াকলাপগুলো ওভারফ্লো/আন্ডারফ্লো হলে স্বয়ংক্রিয়ভাবে রিভার্ট হয়, যা 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 নয়।

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

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

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

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

টোকেন হস্তান্তর

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

ওপেনজেপেলিন নিরাপত্তা সংযোজন

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

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

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

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

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

 

        _beforeTokenTransfer(sender, recipient, amount);

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

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

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

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

 

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

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

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