ERC-20 کنٹریکٹ کا تفصیلی جائزہ
تعارف
ایتھیریم کے سب سے عام استعمالات میں سے ایک کسی گروپ کے لیے قابلِ تجارت ٹوکن بنانا ہے، جو ایک لحاظ سے ان کی اپنی کرنسی ہوتی ہے۔ یہ ٹوکنز عام طور پر ایک معیار کی پیروی کرتے ہیں، 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) ہو، یا سیالیت کا پول جیسا کوئی مختلف کنٹریکٹ ہو۔
اگر آپ ایک تجربہ کار پروگرامر ہیں، تو آپ کو شاید 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)۔ اگر آپ کو کوئی مختلف لائسنس درکار ہے، تو بس اسے تبصروں (comments) میں واضح کر دیں۔
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)۔ یہ کنٹریکٹ میں ٹوکنز کی کل سپلائی واپس کرتا ہے۔ یہ قدر ایتھیریم میں سب سے عام قسم، یعنی unsigned 256 bits کا استعمال کرتے ہوئے واپس کی جاتی ہے (256 bits EVM کا مقامی ورڈ سائز ہے)۔ یہ فنکشن ایک view بھی ہے، جس کا مطلب ہے کہ یہ حالت کو تبدیل نہیں کرتا، لہذا اسے بلاک چین میں موجود ہر نوڈ پر چلانے کے بجائے ایک ہی نوڈ پر عمل میں لایا جا سکتا ہے۔ اس قسم کا فنکشن کوئی ٹرانزیکشن پیدا نہیں کرتا اور اس پر گیس خرچ نہیں ہوتی۔
نوٹ: نظریاتی طور پر ایسا لگ سکتا ہے کہ کنٹریکٹ بنانے والا اصل قدر سے کم کل سپلائی واپس کر کے دھوکہ دے سکتا ہے، جس سے ہر ٹوکن اپنی اصل قدر سے زیادہ قیمتی معلوم ہو۔ تاہم، یہ خوف بلاک چین کی اصل نوعیت کو نظر انداز کرتا ہے۔ بلاک چین پر ہونے والی ہر چیز کی تصدیق ہر نوڈ کے ذریعے کی جا سکتی ہے۔ اسے حاصل کرنے کے لیے، ہر کنٹریکٹ کا مشین لینگویج کوڈ اور اسٹوریج ہر نوڈ پر دستیاب ہوتا ہے۔ اگرچہ آپ کے لیے اپنے کنٹریکٹ کا Solidity کوڈ شائع کرنا لازمی نہیں ہے، لیکن کوئی بھی آپ کو اس وقت تک سنجیدگی سے نہیں لے گا جب تک کہ آپ سورس کوڈ اور Solidity کا وہ ورژن شائع نہ کریں جس کے ساتھ اسے مرتب (compile) کیا گیا تھا، تاکہ آپ کے فراہم کردہ مشین لینگویج کوڈ سے اس کی تصدیق کی جا سکے۔ مثال کے طور پر، یہ کنٹریکٹ دیکھیں (opens in a new tab)۔
/**
* @dev `account` کی ملکیت میں موجود ٹوکنز کی مقدار واپس کرتا ہے۔
*/
function balanceOf(address account) external view returns (uint256);
جیسا کہ نام سے ظاہر ہے، balanceOf کسی اکاؤنٹ کا بیلنس واپس کرتا ہے۔ ایتھیریم اکاؤنٹس کی شناخت Solidity میں address قسم کا استعمال کرتے ہوئے کی جاتی ہے، جو 160 bits پر مشتمل ہوتی ہے۔ یہ بھی 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) کو کتنا الاؤنس خرچ کرنے دیتا ہے۔
/**
* @dev کالر کے ٹوکنز پر `spender` کے الاؤنس کے طور پر `amount` سیٹ کرتا ہے۔
*
* ایک بولین ویلیو واپس کرتا ہے جو بتاتی ہے کہ آیا آپریشن کامیاب ہوا۔
*
* اہم: ہوشیار رہیں کہ اس طریقے سے الاؤنس کو تبدیل کرنے سے یہ خطرہ پیدا ہوتا ہے کہ کوئی بدقسمت ٹرانزیکشن کی ترتیب کی وجہ سے پرانے اور نئے دونوں الاؤنس استعمال کر سکتا ہے۔ اس ریس کنڈیشن کو کم کرنے کا ایک ممکنہ حل یہ ہے کہ پہلے خرچ کرنے والے کا الاؤنس 0 کر دیا جائے اور اس کے بعد مطلوبہ ویلیو سیٹ کی جائے:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* ایک {Approval} ایونٹ خارج کرتا ہے۔
*/
function approve(address spender, uint256 amount) external returns (bool);
approve فنکشن ایک الاؤنس بناتا ہے۔ اس بارے میں پیغام ضرور پڑھیں کہ اس کا غلط استعمال کیسے کیا جا سکتا ہے۔ ایتھیریم میں آپ اپنی ٹرانزیکشنز کی ترتیب کو کنٹرول کرتے ہیں، لیکن آپ اس ترتیب کو کنٹرول نہیں کر سکتے جس میں دوسرے لوگوں کی ٹرانزیکشنز پر عمل درآمد ہوگا، جب تک کہ آپ اپنی ٹرانزیکشن اس وقت تک جمع نہ کرائیں جب تک کہ آپ یہ نہ دیکھ لیں کہ دوسرے فریق کی ٹرانزیکشن ہو چکی ہے۔
/**
* @dev الاؤنس کے طریقہ کار کا استعمال کرتے ہوئے `sender` سے `recipient` کو `amount` ٹوکنز منتقل کرتا ہے۔ پھر کالر کے الاؤنس سے `amount` کی کٹوتی کی جاتی ہے۔
*
* ایک بولین ویلیو واپس کرتا ہے جو بتاتی ہے کہ آیا آپریشن کامیاب ہوا۔
*
* ایک {Transfer} ایونٹ خارج کرتا ہے۔
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
آخر میں، transferFrom خرچ کرنے والے کے ذریعے الاؤنس کو درحقیقت خرچ کرنے کے لیے استعمال کیا جاتا ہے۔
/**
* @dev تب خارج ہوتا ہے جب `value` ٹوکنز ایک اکاؤنٹ (`from`) سے دوسرے (`to`) میں منتقل کیے جاتے ہیں۔
*
* نوٹ کریں کہ `value` صفر ہو سکتی ہے۔
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev تب خارج ہوتا ہے جب {approve} کی کال کے ذریعے کسی `owner` کے لیے `spender` کا الاؤنس سیٹ کیا جاتا ہے۔ `value` نیا الاؤنس ہے۔
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
یہ ایونٹس اس وقت خارج ہوتے ہیں جب ERC-20 کنٹریکٹ کی حالت تبدیل ہوتی ہے۔
اصل کنٹریکٹ
یہ وہ اصل کنٹریکٹ ہے جو ERC-20 معیار کو نافذ کرتا ہے، جسے یہاں سے لیا گیا ہے (opens in a new tab)۔ اسے جوں کا توں استعمال کرنے کا ارادہ نہیں ہے، لیکن آپ اسے قابل استعمال بنانے کے لیے اس سے وراثت (inherit) (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 غیر ضروری ہو جاتا ہے۔ یہ کنٹریکٹ پرانے کمپائلر ورژنز کے ساتھ پچھلی مطابقت (backward compatibility) کے لیے SafeMath کا استعمال کرتا ہے۔
یہ تبصرہ کنٹریکٹ کے مقصد کی وضاحت کرتا ہے۔
/**
* @dev {IERC20} انٹرفیس کا نفاذ۔
*
* یہ نفاذ اس بات سے لاپرواہ ہے کہ ٹوکنز کیسے بنائے جاتے ہیں۔ اس کا مطلب ہے کہ {_mint} کا استعمال کرتے ہوئے اخذ کردہ کنٹریکٹ میں سپلائی کا طریقہ کار شامل کرنا ہوگا۔
* عمومی طریقہ کار کے لیے {ERC20PresetMinterPauser} دیکھیں۔
*
* ٹپ: تفصیلی تحریر کے لیے ہماری گائیڈ دیکھیں
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* ہم نے اوپن زیپلن کی عمومی ہدایات کی پیروی کی ہے: فنکشنز ناکامی پر `false` واپس کرنے کے بجائے ریورٹ (revert) ہو جاتے ہیں۔ یہ رویہ بہرحال روایتی ہے اور ERC-20 ایپلی کیشنز کی توقعات سے متصادم نہیں ہے۔
*
* مزید برآں، {transferFrom} کی کالز پر ایک {Approval} ایونٹ خارج ہوتا ہے۔
* یہ ایپلی کیشنز کو صرف مذکورہ ایونٹس کو سن کر تمام اکاؤنٹس کے لیے الاؤنس کو دوبارہ بنانے کی اجازت دیتا ہے۔ EIP کے دیگر نفاذ شاید ان ایونٹس کو خارج نہ کریں، کیونکہ یہ تصریح کے ذریعہ درکار نہیں ہے۔
*
* آخر میں، الاؤنسز سیٹ کرنے کے حوالے سے معروف مسائل کو کم کرنے کے لیے غیر معیاری {decreaseAllowance} اور {increaseAllowance}
* فنکشنز شامل کیے گئے ہیں۔ {IERC20-approve} دیکھیں۔
*/
کنٹریکٹ کی تعریف
contract ERC20 is Context, IERC20 {
یہ لائن وراثت کی وضاحت کرتی ہے، اس صورت میں اوپر سے IERC20 اور OpenGSN کے لیے Context سے۔
using SafeMath for uint256;
یہ لائن SafeMath لائبریری کو uint256 قسم کے ساتھ منسلک کرتی ہے۔ آپ یہ لائبریری یہاں (opens in a new tab) تلاش کر سکتے ہیں۔
متغیرات کی تعریفیں
یہ تعریفیں کنٹریکٹ کے حالت کے متغیرات (state variables) کی وضاحت کرتی ہیں۔ ان متغیرات کو private قرار دیا گیا ہے، لیکن اس کا مطلب صرف یہ ہے کہ بلاک چین پر موجود دیگر کنٹریکٹس انہیں پڑھ نہیں سکتے۔ بلاک چین پر کوئی راز نہیں ہوتے، ہر نوڈ پر موجود سافٹ ویئر کے پاس ہر بلاک پر ہر کنٹریکٹ کی حالت ہوتی ہے۔ روایت کے مطابق، حالت کے متغیرات کا نام _<something> رکھا جاتا ہے۔
پہلے دو متغیرات میپنگز (mappings) (opens in a new tab) ہیں، جس کا مطلب ہے کہ وہ تقریباً ایسوسی ایٹو اریز (associative arrays) (opens in a new tab) کی طرح برتاؤ کرتے ہیں، سوائے اس کے کہ کیز (keys) عددی قدریں ہوتی ہیں۔ اسٹوریج صرف ان اندراجات کے لیے مختص کی جاتی ہے جن کی قدریں ڈیفالٹ (صفر) سے مختلف ہوتی ہیں۔
mapping (address => uint256) private _balances;
پہلی میپنگ، _balances، پتے اور اس ٹوکن کے ان کے متعلقہ بیلنس ہیں۔ بیلنس تک رسائی کے لیے، یہ نحو (syntax) استعمال کریں: _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 نہیں ہے۔
ایک طرف، ایتھیریم میں فلوٹنگ پوائنٹ یا کسر والے متغیرات نہیں ہوتے ہیں۔ دوسری طرف، انسان ٹوکنز کو تقسیم کرنے کے قابل ہونا پسند کرتے ہیں۔ لوگوں کے کرنسی کے طور پر سونے پر متفق ہونے کی ایک وجہ یہ تھی کہ جب کوئی بطخ کی قیمت کے برابر گائے خریدنا چاہتا تھا تو ریزگاری (change) بنانا مشکل تھا۔
اس کا حل یہ ہے کہ انٹیجرز (integers) کا ریکارڈ رکھا جائے، لیکن اصلی ٹوکن کے بجائے ایک کسر والے ٹوکن کو گنا جائے جو تقریباً بے وقعت ہو۔ ایتھر کے معاملے میں، کسر والے ٹوکن کو 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؟ ایتھر کے معاملے میں یہ 10^18 Wei فی ETH بیان کیا گیا ہے، لیکن اپنے ٹوکن کے لیے آپ ایک مختلف قدر منتخب کر سکتے ہیں۔ اگر ٹوکن کو تقسیم کرنے کا کوئی مطلب نہیں بنتا، تو آپ _decimals کی قدر صفر استعمال کر سکتے ہیں۔ اگر آپ ETH جیسا ہی معیار استعمال کرنا چاہتے ہیں، تو قدر 18 استعمال کریں۔
کنسٹرکٹر
/**
* @dev {name} اور {symbol} کے لیے ویلیوز سیٹ کرتا ہے، {decimals} کو 18 کی ڈیفالٹ ویلیو کے ساتھ شروع کرتا ہے۔
*
* {decimals} کے لیے مختلف ویلیو منتخب کرنے کے لیے، {_setupDecimals} استعمال کریں۔
*
* یہ تینوں ویلیوز ناقابلِ تغیر ہیں: انہیں کنسٹرکشن کے دوران صرف ایک بار سیٹ کیا جا سکتا ہے۔
*/
constructor (string memory name_, string memory symbol_) public {
// Solidity ≥0.7.0 میں، 'public' مضمر ہے اور اسے چھوڑا جا سکتا ہے۔
_name = name_;
_symbol = symbol_;
_decimals = 18;
}
کنسٹرکٹر کو اس وقت کال کیا جاتا ہے جب کنٹریکٹ پہلی بار بنایا جاتا ہے۔ روایت کے مطابق، فنکشن کے پیرامیٹرز کا نام <something>_ رکھا جاتا ہے۔
یوزر انٹرفیس فنکشنز
/**
* @dev ٹوکن کا نام واپس کرتا ہے۔
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev ٹوکن کی علامت واپس کرتا ہے، جو عام طور پر نام کا ایک چھوٹا ورژن ہوتا ہے۔
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev اس کی صارف کی نمائندگی حاصل کرنے کے لیے استعمال ہونے والے اعشاریوں کی تعداد واپس کرتا ہے۔
* مثال کے طور پر، اگر `decimals` کی ویلیو `2` ہے، تو `505` ٹوکنز کا بیلنس
* صارف کو `5,05` (`505 / 10 ** 2`) کے طور پر دکھایا جانا چاہیے۔
*
* ٹوکنز عام طور پر 18 کی ویلیو کا انتخاب کرتے ہیں، جو ایتھر اور Wei کے درمیان تعلق کی نقل کرتا ہے۔ یہ وہ ویلیو ہے جو {ERC20} استعمال کرتا ہے، جب تک کہ {_setupDecimals} کو
* کال نہ کیا جائے۔
*
* نوٹ: یہ معلومات صرف _دکھانے_ کے مقاصد کے لیے استعمال ہوتی ہے: یہ
* کسی بھی طرح کنٹریکٹ کے حساب کتاب کو متاثر نہیں کرتی، بشمول
* {IERC20-balanceOf} اور {IERC20-transfer}۔
*/
function decimals() public view returns (uint8) {
return _decimals;
}
یہ فنکشنز، 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];
}
کسی اکاؤنٹ کا بیلنس پڑھیں۔ نوٹ کریں کہ کسی کو بھی کسی دوسرے کے اکاؤنٹ کا بیلنس حاصل کرنے کی اجازت ہے۔ اس معلومات کو چھپانے کی کوشش کرنے کا کوئی فائدہ نہیں ہے، کیونکہ یہ ویسے بھی ہر نوڈ پر دستیاب ہے۔ بلاک چین پر کوئی راز نہیں ہوتے۔
ٹوکنز کی منتقلی
/**
* @dev {IERC20-transfer} دیکھیں۔
*
* تقاضے:
*
* - `recipient` صفر ایڈریس نہیں ہو سکتا۔
* - کالر کے پاس کم از کم `amount` کا بیلنس ہونا چاہیے۔
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
transfer فنکشن بھیجنے والے کے اکاؤنٹ سے کسی دوسرے اکاؤنٹ میں ٹوکنز کی منتقلی کے لیے کال کیا جاتا ہے۔ نوٹ کریں کہ اگرچہ یہ ایک بولین (boolean) قدر واپس کرتا ہے، لیکن وہ قدر ہمیشہ 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۔
الاؤنس فنکشن
/**
* @dev {IERC20-allowance} دیکھیں۔
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
allowance فنکشن ہر کسی کو کسی بھی الاؤنس کو چیک کرنے کی اجازت دیتا ہے۔
منظور کرنے کا فنکشن
/**
* @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 فنکشن
یہ وہ فنکشن ہے جسے خرچ کرنے والا الاؤنس خرچ کرنے کے لیے کال کرتا ہے۔ اس کے لیے دو کارروائیوں کی ضرورت ہوتی ہے: خرچ کی جانے والی رقم کو منتقل کرنا اور الاؤنس کو اس رقم سے کم کرنا۔
/**
* @dev {IERC20-transferFrom} دیکھیں۔
*
* اپ ڈیٹ شدہ الاؤنس کی نشاندہی کرنے والا ایک {Approval} ایونٹ خارج کرتا ہے۔ یہ EIP کے ذریعہ
* درکار نہیں ہے۔ {ERC20} کے شروع میں نوٹ دیکھیں۔
*
* تقاضے:
*
* - `sender` اور `recipient` صفر ایڈریس نہیں ہو سکتے۔
* - `sender` کے پاس کم از کم `amount` کا بیلنس ہونا چاہیے۔
* - کالر کے پاس ``sender`` کے ٹوکنز کے لیے کم از کم
* `amount` کا الاؤنس ہونا چاہیے۔
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual
override returns (bool) {
_transfer(sender, recipient, amount);
a.sub(b, "message") فنکشن کال دو کام کرتی ہے۔ پہلا، یہ a-b کا حساب لگاتی ہے، جو نیا الاؤنس ہے۔ دوسرا، یہ چیک کرتی ہے کہ یہ نتیجہ منفی تو نہیں ہے۔ اگر یہ منفی ہے تو کال فراہم کردہ پیغام کے ساتھ ریورٹ ہو جاتی ہے۔ نوٹ کریں کہ جب کوئی کال ریورٹ ہوتی ہے تو اس کال کے دوران پہلے کی گئی کسی بھی پروسیسنگ کو نظر انداز کر دیا جاتا ہے لہذا ہمیں _transfer کو ان ڈو (undo) کرنے کی ضرورت نہیں ہے۔
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
"ERC20: transfer amount exceeds allowance"));
return true;
}
اوپن زیپلن کے حفاظتی اضافے
کسی غیر صفر الاؤنس کو دوسری غیر صفر قدر پر سیٹ کرنا خطرناک ہے، کیونکہ آپ صرف اپنی ٹرانزیکشنز کی ترتیب کو کنٹرول کرتے ہیں، کسی اور کی نہیں۔ تصور کریں کہ آپ کے پاس دو صارفین ہیں، ایلس جو سادہ لوح ہے اور بل جو بے ایمان ہے۔ ایلس بل سے کچھ سروس چاہتی ہے، جس کی قیمت اس کے خیال میں پانچ ٹوکنز ہے - لہذا وہ بل کو پانچ ٹوکنز کا الاؤنس دیتی ہے۔
پھر کچھ تبدیل ہوتا ہے اور بل کی قیمت بڑھ کر دس ٹوکنز ہو جاتی ہے۔ ایلس، جو اب بھی سروس چاہتی ہے، ایک ٹرانزیکشن بھیجتی ہے جو بل کا الاؤنس دس پر سیٹ کر دیتی ہے۔ جس لمحے بل ٹرانزیکشن پول میں اس نئی ٹرانزیکشن کو دیکھتا ہے وہ ایک ٹرانزیکشن بھیجتا ہے جو ایلس کے پانچ ٹوکنز خرچ کرتی ہے اور اس کی گیس کی قیمت بہت زیادہ ہوتی ہے تاکہ اسے تیزی سے مائن کیا جا سکے۔ اس طرح بل پہلے پانچ ٹوکنز خرچ کر سکتا ہے اور پھر، جب ایلس کا نیا الاؤنس مائن ہو جاتا ہے، تو مزید دس خرچ کر سکتا ہے جس کی کل قیمت پندرہ ٹوکنز بنتی ہے، جو کہ ایلس کی اجازت سے زیادہ ہے۔ اس تکنیک کو فرنٹ رننگ (opens in a new tab) کہا جاتا ہے۔
| ایلس کی ٹرانزیکشن | ایلس کا نانس | بل کی ٹرانزیکشن | بل کا نانس | بل کا الاؤنس | ایلس سے بل کی کل آمدنی |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| approve(Bill, 10) | 11 | 10 | 5 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 15 |
اس مسئلے سے بچنے کے لیے، یہ دو فنکشنز (increaseAllowance اور decreaseAllowance) آپ کو الاؤنس میں ایک مخصوص رقم سے ترمیم کرنے کی اجازت دیتے ہیں۔ لہذا اگر بل پہلے ہی پانچ ٹوکنز خرچ کر چکا ہے، تو وہ صرف مزید پانچ خرچ کر سکے گا۔ وقت کے لحاظ سے، اس کے کام کرنے کے دو طریقے ہیں، اور دونوں صورتوں میں بل کو صرف دس ٹوکنز ہی ملیں گے:
A:
| ایلس کی ٹرانزیکشن | ایلس کا نانس | بل کی ٹرانزیکشن | بل کا نانس | بل کا الاؤنس | ایلس سے بل کی کل آمدنی |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| increaseAllowance(Bill, 5) | 11 | 0+5 = 5 | 5 | ||
| transferFrom(Alice, Bill, 5) | 10,124 | 0 | 10 |
B:
| ایلس کی ٹرانزیکشن | ایلس کا نانس | بل کی ٹرانزیکشن | بل کا نانس | بل کا الاؤنس | ایلس سے بل کی کل آمدنی |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| increaseAllowance(Bill, 5) | 11 | 5+5 = 10 | 0 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 10 |
/**
* @dev کالر کی طرف سے `spender` کو دیے گئے الاؤنس میں ایٹمی طور پر اضافہ کرتا ہے۔
*
* یہ {approve} کا متبادل ہے جسے {IERC20-approve} میں بیان کردہ
* مسائل کو کم کرنے کے لیے استعمال کیا جا سکتا ہے۔
*
* اپ ڈیٹ شدہ الاؤنس کی نشاندہی کرنے والا ایک {Approval} ایونٹ خارج کرتا ہے۔
*
* تقاضے:
*
* - `spender` صفر ایڈریس نہیں ہو سکتا۔
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
a.add(b) فنکشن ایک محفوظ اضافہ (safe add) ہے۔ اس غیر امکانی صورت میں کہ a+b>=2^256 ہو، یہ اس طرح ریپ اراؤنڈ (wrap around) نہیں کرتا جیسے عام اضافہ کرتا ہے۔
/**
* @dev کالر کی طرف سے `spender` کو دیے گئے الاؤنس میں ایٹمی طور پر کمی کرتا ہے۔
*
* یہ {approve} کا متبادل ہے جسے {IERC20-approve} میں بیان کردہ
* مسائل کو کم کرنے کے لیے استعمال کیا جا سکتا ہے۔
*
* اپ ڈیٹ شدہ الاؤنس کی نشاندہی کرنے والا ایک {Approval} ایونٹ خارج کرتا ہے۔
*
* تقاضے:
*
* - `spender` صفر ایڈریس نہیں ہو سکتا۔
* - `spender` کے پاس کالر کے لیے کم از کم
* `subtractedValue` کا الاؤنس ہونا چاہیے۔
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue,
"ERC20: decreased allowance below zero"));
return true;
}
ٹوکن کی معلومات میں ترمیم کرنے والے فنکشنز
یہ وہ چار فنکشنز ہیں جو اصل کام کرتے ہیں: _transfer، _mint، _burn، اور _approve۔
_transfer فنکشن
/**
* @dev `sender` سے `recipient` کو `amount` ٹوکنز منتقل کرتا ہے۔
*
* یہ اندرونی فنکشن {transfer} کے مساوی ہے، اور اسے استعمال کیا جا سکتا ہے
* مثال کے طور پر، خودکار ٹوکن فیس، سلیشنگ کے طریقہ کار وغیرہ کو نافذ کرنے کے لیے۔
*
* ایک {Transfer} ایونٹ خارج کرتا ہے۔
*
* تقاضے:
*
* - `sender` صفر ایڈریس نہیں ہو سکتا۔
* - `recipient` صفر ایڈریس نہیں ہو سکتا۔
* - `sender` کے پاس کم از کم `amount` کا بیلنس ہونا چاہیے۔
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
یہ فنکشن، _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);
اس کنٹریکٹ کو استعمال کرنے کے دو طریقے ہیں:
- اسے اپنے کوڈ کے لیے ٹیمپلیٹ کے طور پر استعمال کریں
- اس سے وراثت لیں (opens in a new tab)، اور صرف ان فنکشنز کو اوور رائیڈ (override) کریں جن میں آپ کو ترمیم کرنے کی ضرورت ہے
دوسرا طریقہ بہت بہتر ہے کیونکہ اوپن زیپلن ERC-20 کوڈ کا پہلے ہی آڈٹ کیا جا چکا ہے اور اسے محفوظ دکھایا گیا ہے۔ جب آپ وراثت کا استعمال کرتے ہیں تو یہ واضح ہوتا ہے کہ آپ کن فنکشنز میں ترمیم کرتے ہیں، اور آپ کے کنٹریکٹ پر بھروسہ کرنے کے لیے لوگوں کو صرف ان مخصوص فنکشنز کا آڈٹ کرنے کی ضرورت ہوتی ہے۔
ہر بار جب ٹوکنز ہاتھ بدلتے ہیں تو کوئی فنکشن انجام دینا اکثر مفید ہوتا ہے۔ تاہم، _transfer ایک بہت اہم فنکشن ہے اور اسے غیر محفوظ طریقے سے لکھنا ممکن ہے (نیچے دیکھیں)، لہذا بہتر ہے کہ اسے اوور رائیڈ نہ کیا جائے۔ اس کا حل _beforeTokenTransfer ہے، جو ایک ہک فنکشن (hook function) (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 کو کال کرے گا۔
/** @dev `amount` ٹوکنز بناتا ہے اور انہیں `account` کو تفویض کرتا ہے، جس سے
* کل سپلائی میں اضافہ ہوتا ہے۔
*
* ایک {Transfer} ایونٹ خارج کرتا ہے جس میں `from` کو صفر ایڈریس پر سیٹ کیا گیا ہو۔
*
* تقاضے:
*
* - `to` صفر ایڈریس نہیں ہو سکتا۔
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
جب ٹوکنز کی کل تعداد تبدیل ہو تو _totalSupply کو اپ ڈیٹ کرنا یقینی بنائیں۔
/**
* @dev `account` سے `amount` ٹوکنز کو تباہ کرتا ہے، جس سے
* کل سپلائی کم ہو جاتی ہے۔
*
* ایک {Transfer} ایونٹ خارج کرتا ہے جس میں `to` کو صفر ایڈریس پر سیٹ کیا گیا ہو۔
*
* تقاضے:
*
* - `account` صفر ایڈریس نہیں ہو سکتا۔
* - `account` کے پاس کم از کم `amount` ٹوکنز ہونے چاہئیں۔
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
_burn فنکشن تقریباً _mint جیسا ہی ہے، سوائے اس کے کہ یہ دوسری سمت میں جاتا ہے۔
_approve فنکشن
یہ وہ فنکشن ہے جو دراصل الاؤنسز کی وضاحت کرتا ہے۔ نوٹ کریں کہ یہ مالک کو ایسا الاؤنس بتانے کی اجازت دیتا ہے جو مالک کے موجودہ بیلنس سے زیادہ ہو۔ یہ ٹھیک ہے کیونکہ بیلنس منتقلی کے وقت چیک کیا جاتا ہے، جب یہ اس بیلنس سے مختلف ہو سکتا ہے جو الاؤنس بناتے وقت تھا۔
/**
* @dev `owner` کے ٹوکنز پر `spender` کے الاؤنس کے طور پر `amount` سیٹ کرتا ہے۔
*
* یہ اندرونی فنکشن `approve` کے مساوی ہے، اور اسے استعمال کیا جا سکتا ہے
* مثال کے طور پر، کچھ ذیلی سسٹمز کے لیے خودکار الاؤنسز سیٹ کرنے کے لیے، وغیرہ۔
*
* ایک {Approval} ایونٹ خارج کرتا ہے۔
*
* تقاضے:
*
* - `owner` صفر ایڈریس نہیں ہو سکتا۔
* - `spender` صفر ایڈریس نہیں ہو سکتا۔
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
ایک Approval ایونٹ خارج کریں۔ ایپلی کیشن کے لکھے جانے کے طریقے پر منحصر ہے، خرچ کرنے والے کنٹریکٹ کو منظوری کے بارے میں یا تو مالک کے ذریعے بتایا جا سکتا ہے یا کسی ایسے سرور کے ذریعے جو ان ایونٹس کو سنتا ہے۔
emit Approval(owner, spender, amount);
}
Decimals متغیر میں ترمیم کریں
/**
* @dev {decimals} کو 18 کی ڈیفالٹ ویلیو کے علاوہ کسی اور ویلیو پر سیٹ کرتا ہے۔
*
* انتباہ: اس فنکشن کو صرف کنسٹرکٹر سے کال کیا جانا چاہیے۔ زیادہ تر
* ایپلی کیشنز جو ٹوکن کنٹریکٹس کے ساتھ تعامل کرتی ہیں وہ یہ توقع نہیں کریں گی کہ
* {decimals} کبھی تبدیل ہوں گے، اور اگر ایسا ہوتا ہے تو وہ غلط طریقے سے کام کر سکتی ہیں۔
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
یہ فنکشن _decimals متغیر میں ترمیم کرتا ہے جو یوزر انٹرفیسز کو یہ بتانے کے لیے استعمال ہوتا ہے کہ رقم کی تشریح کیسے کی جائے۔ آپ کو اسے کنسٹرکٹر سے کال کرنا چاہیے۔ اسے کسی بھی بعد کے موقع پر کال کرنا بے ایمانی ہوگی، اور ایپلی کیشنز کو اسے سنبھالنے کے لیے ڈیزائن نہیں کیا گیا ہے۔
ہکس (Hooks)
/**
* @dev ہک (Hook) جسے ٹوکنز کی کسی بھی منتقلی سے پہلے کال کیا جاتا ہے۔ اس میں
* منٹنگ اور برننگ شامل ہیں۔
*
* کال کرنے کی شرائط:
*
* - جب `from` اور `to` دونوں غیر صفر ہوں، تو ``from`` کے `amount` ٹوکنز
* `to` کو منتقل کیے جائیں گے۔
* - جب `from` صفر ہو، تو `to` کے لیے `amount` ٹوکنز منٹ کیے جائیں گے۔
* - جب `to` صفر ہو، تو ``from`` کے `amount` ٹوکنز برن کیے جائیں گے۔
* - `from` اور `to` کبھی بھی دونوں صفر نہیں ہوتے۔
*
* ہکس کے بارے میں مزید جاننے کے لیے، xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks] پر جائیں۔
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
یہ وہ ہک فنکشن ہے جسے منتقلی کے دوران کال کیا جانا ہے۔ یہ یہاں خالی ہے، لیکن اگر آپ کو اس سے کچھ کروانے کی ضرورت ہے تو آپ بس اسے اوور رائیڈ کر دیں۔
نتیجہ
جائزہ لینے کے لیے، اس کنٹریکٹ میں کچھ اہم ترین خیالات یہ ہیں (میری رائے میں، آپ کی رائے مختلف ہو سکتی ہے):
- بلاک چین پر کوئی راز نہیں ہوتے۔ کوئی بھی معلومات جس تک سمارٹ کنٹریکٹ رسائی حاصل کر سکتا ہے وہ پوری دنیا کے لیے دستیاب ہے۔
- آپ اپنی ٹرانزیکشنز کی ترتیب کو کنٹرول کر سکتے ہیں، لیکن یہ نہیں کہ دوسرے لوگوں کی ٹرانزیکشنز کب ہوتی ہیں۔ یہی وجہ ہے کہ الاؤنس کو تبدیل کرنا خطرناک ہو سکتا ہے، کیونکہ یہ خرچ کرنے والے کو دونوں الاؤنسز کا مجموعہ خرچ کرنے دیتا ہے۔
uint256قسم کی قدریں ریپ اراؤنڈ (wrap around) ہوتی ہیں۔ دوسرے الفاظ میں، 0-1=2^256-1۔ اگر یہ مطلوبہ رویہ نہیں ہے، تو آپ کو اسے چیک کرنا ہوگا (یا SafeMath لائبریری استعمال کریں جو آپ کے لیے یہ کرتی ہے)۔ نوٹ کریں کہ یہ Solidity 0.8.0 (opens in a new tab) میں تبدیل ہو گیا ہے۔- ایک مخصوص قسم کی تمام حالت کی تبدیلیاں ایک مخصوص جگہ پر کریں، کیونکہ اس سے آڈٹ کرنا آسان ہو جاتا ہے۔ یہی وجہ ہے کہ ہمارے پاس، مثال کے طور پر،
_approveہے، جسےapprove،transferFrom،increaseAllowance، اورdecreaseAllowanceکے ذریعے کال کیا جاتا ہے۔ - حالت کی تبدیلیاں ایٹمی (atomic) ہونی چاہئیں، ان کے درمیان کسی اور کارروائی کے بغیر (جیسا کہ آپ
_transferمیں دیکھ سکتے ہیں)۔ اس کی وجہ یہ ہے کہ حالت کی تبدیلی کے دوران آپ کے پاس ایک غیر مستقل حالت ہوتی ہے۔ مثال کے طور پر، اس وقت کے درمیان جب آپ بھیجنے والے کے بیلنس سے کٹوتی کرتے ہیں اور اس وقت کے درمیان جب آپ وصول کنندہ کے بیلنس میں اضافہ کرتے ہیں، وجود میں آنے والے ٹوکنز کی تعداد اس سے کم ہوتی ہے جتنی ہونی چاہیے۔ اگر ان کے درمیان کارروائیاں ہوں، خاص طور پر کسی مختلف کنٹریکٹ کو کالز، تو اس کا ممکنہ طور پر غلط استعمال کیا جا سکتا ہے۔
اب جب کہ آپ نے دیکھ لیا ہے کہ اوپن زیپلن ERC-20 کنٹریکٹ کیسے لکھا جاتا ہے، اور خاص طور پر اسے کیسے زیادہ محفوظ بنایا جاتا ہے، تو جائیں اور اپنے محفوظ کنٹریکٹس اور ایپلی کیشنز لکھیں۔
