سکیم ٹوکنز کے ذریعے استعمال ہونے والی کچھ چالیں اور انہیں کیسے پہچانا جائے
اس ٹیوٹوریل میں ہم ایک سکیم ٹوکن (opens in a new tab) کا تجزیہ کرتے ہیں تاکہ یہ دیکھ سکیں کہ سکیمرز کون سی چالیں چلتے ہیں اور وہ انہیں کیسے نافذ کرتے ہیں۔ ٹیوٹوریل کے اختتام تک آپ کو ERC-20 ٹوکن کنٹریکٹس، ان کی صلاحیتوں، اور شکوک و شبہات کی ضرورت کے بارے میں زیادہ جامع نظریہ حاصل ہو جائے گا۔ پھر ہم اس سکیم ٹوکن کے ذریعے خارج ہونے والے ایونٹس کو دیکھتے ہیں اور یہ جاننے کی کوشش کرتے ہیں کہ ہم خودکار طریقے سے کیسے پہچان سکتے ہیں کہ یہ جائز نہیں ہے۔
سکیم ٹوکنز - یہ کیا ہیں، لوگ انہیں کیوں بناتے ہیں، اور ان سے کیسے بچا جائے
ایتھیریم کا ایک سب سے عام استعمال کسی گروپ کے لیے ایک قابل تجارت ٹوکن بنانا ہے، جو ایک لحاظ سے ان کی اپنی کرنسی ہوتی ہے۔ تاہم، جہاں کہیں بھی جائز استعمال کے معاملات ہوتے ہیں جو قدر لاتے ہیں، وہاں ایسے مجرم بھی ہوتے ہیں جو اس قدر کو اپنے لیے چرانے کی کوشش کرتے ہیں۔
آپ صارف کے نقطہ نظر سے اس موضوع کے بارے میں مزید ethereum.org پر کہیں اور پڑھ سکتے ہیں۔ یہ ٹیوٹوریل ایک سکیم ٹوکن کا تجزیہ کرنے پر مرکوز ہے تاکہ یہ دیکھا جا سکے کہ یہ کیسے کیا جاتا ہے اور اسے کیسے پہچانا جا سکتا ہے۔
مجھے کیسے معلوم ہوگا کہ wARB ایک سکیم ہے؟
جس ٹوکن کا ہم تجزیہ کر رہے ہیں وہ wARB (opens in a new tab) ہے، جو جائز ARB ٹوکن (opens in a new tab) کے برابر ہونے کا دکھاوا کرتا ہے۔
یہ جاننے کا سب سے آسان طریقہ کہ کون سا ٹوکن جائز ہے، اس کی بانی تنظیم، آربٹرم (opens in a new tab) کو دیکھنا ہے۔ جائز پتے ان کی دستاویزات میں (opens in a new tab) بیان کیے گئے ہیں۔
سورس کوڈ کیوں دستیاب ہے؟
عام طور پر ہم توقع کرتے ہیں کہ جو لوگ دوسروں کو دھوکہ دینے کی کوشش کرتے ہیں وہ خفیہ رہتے ہیں، اور واقعی بہت سے سکیم ٹوکنز کا کوڈ دستیاب نہیں ہوتا (مثال کے طور پر، یہ والا (opens in a new tab) اور یہ والا (opens in a new tab))۔
تاہم، جائز ٹوکنز عام طور پر اپنا سورس کوڈ شائع کرتے ہیں، اس لیے جائز نظر آنے کے لیے سکیم ٹوکنز کے مصنفین بھی بعض اوقات ایسا ہی کرتے ہیں۔ wARB (opens in a new tab) ان ٹوکنز میں سے ایک ہے جن کا سورس کوڈ دستیاب ہے، جس سے اسے سمجھنا آسان ہو جاتا ہے۔
اگرچہ کنٹریکٹ ڈیپلائرز یہ انتخاب کر سکتے ہیں کہ سورس کوڈ شائع کرنا ہے یا نہیں، لیکن وہ غلط سورس کوڈ شائع نہیں کر سکتے۔ بلاک ایکسپلورر فراہم کردہ سورس کوڈ کو آزادانہ طور پر مرتب کرتا ہے، اور اگر اسے بالکل وہی بائٹ کوڈ نہیں ملتا، تو وہ اس سورس کوڈ کو مسترد کر دیتا ہے۔ آپ Etherscan کی سائٹ پر اس کے بارے میں مزید پڑھ سکتے ہیں (opens in a new tab)۔
جائز ERC-20 ٹوکنز سے موازنہ
ہم اس ٹوکن کا جائز ERC-20 ٹوکنز سے موازنہ کرنے جا رہے ہیں۔ اگر آپ اس بات سے واقف نہیں ہیں کہ جائز ERC-20 ٹوکنز عام طور پر کیسے لکھے جاتے ہیں، تو یہ ٹیوٹوریل دیکھیں۔
مراعات یافتہ پتوں کے لیے مستقلات (Constants)
کنٹریکٹس کو بعض اوقات مراعات یافتہ پتوں کی ضرورت ہوتی ہے۔ وہ کنٹریکٹس جو طویل مدتی استعمال کے لیے بنائے گئے ہیں، کسی مراعات یافتہ پتے کو ان پتوں کو تبدیل کرنے کی اجازت دیتے ہیں، مثال کے طور پر ایک نئے ملٹی سگ کنٹریکٹ کے استعمال کو فعال کرنے کے لیے۔ ایسا کرنے کے کئی طریقے ہیں۔
HOP ٹوکن کنٹریکٹ (opens in a new tab) Ownable (opens in a new tab) پیٹرن کا استعمال کرتا ہے۔ مراعات یافتہ پتہ سٹوریج میں، _owner نامی فیلڈ میں رکھا جاتا ہے (تیسری فائل، Ownable.sol دیکھیں)۔
abstract contract Ownable is Context {
address private _owner;
.
.
.
}
ARB ٹوکن کنٹریکٹ (opens in a new tab) میں براہ راست کوئی مراعات یافتہ پتہ نہیں ہوتا۔ تاہم، اسے اس کی ضرورت بھی نہیں ہے۔ یہ پتہ 0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 (opens in a new tab) پر ایک proxy (opens in a new tab) کے پیچھے موجود ہوتا ہے۔ اس کنٹریکٹ کا ایک مراعات یافتہ پتہ ہے (چوتھی فائل، ERC1967Upgrade.sol دیکھیں) جسے اپ گریڈ کے لیے استعمال کیا جا سکتا ہے۔
/**
* @dev EIP1967 ایڈمن سلاٹ میں ایک نیا پتہ اسٹور کرتا ہے۔
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
اس کے برعکس، wARB کنٹریکٹ میں ایک ہارڈ کوڈڈ contract_owner ہوتا ہے۔
contract WrappedArbitrum is Context, IERC20 {
.
.
.
address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1;
address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33;
.
.
.
}
یہ کنٹریکٹ کا مالک (opens in a new tab) کوئی ایسا کنٹریکٹ نہیں ہے جسے مختلف اوقات میں مختلف اکاؤنٹس کے ذریعے کنٹرول کیا جا سکے، بلکہ یہ ایک بیرونی ملکیت والا اکاؤنٹ ہے۔ اس کا مطلب یہ ہے کہ اسے شاید کسی فرد کے ذریعے قلیل مدتی استعمال کے لیے ڈیزائن کیا گیا ہے، نہ کہ ایک ERC-20 کو کنٹرول کرنے کے طویل مدتی حل کے طور پر جو قیمتی رہے گا۔
اور واقعی، اگر ہم Etherscan میں دیکھیں تو ہمیں معلوم ہوتا ہے کہ سکیمر نے اس کنٹریکٹ کو 19 مئی 2023 کے دوران صرف 12 گھنٹے (پہلی ٹرانزیکشن (opens in a new tab) سے آخری ٹرانزیکشن (opens in a new tab) تک) استعمال کیا۔
جعلی _transfer فنکشن
یہ ایک معیاری طریقہ ہے کہ اصل منتقلی ایک اندرونی _transfer فنکشن کا استعمال کرتے ہوئے ہو۔
wARB میں یہ فنکشن تقریباً جائز لگتا ہے:
function _transfer(address sender, address recipient, uint256 amount) internal virtual{
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
}
مشکوک حصہ یہ ہے:
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
اگر کنٹریکٹ کا مالک ٹوکن بھیجتا ہے، تو Transfer ایونٹ یہ کیوں دکھاتا ہے کہ وہ deployer سے آئے ہیں؟
تاہم، ایک زیادہ اہم مسئلہ ہے۔ اس _transfer فنکشن کو کون کال کرتا ہے? اسے باہر سے کال نہیں کیا جا سکتا، اسے internal کے طور پر نشان زد کیا گیا ہے۔ اور ہمارے پاس موجود کوڈ میں _transfer کی کوئی کال شامل نہیں ہے۔ واضح طور پر، یہ یہاں ایک دھوکے کے طور پر موجود ہے۔
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_f_(_msgSender(), recipient, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
_f_(sender, recipient, amount);
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
return true;
}
جب ہم ان فنکشنز کو دیکھتے ہیں جنہیں ٹوکنز کی منتقلی کے لیے کال کیا جاتا ہے، transfer اور transferFrom، تو ہم دیکھتے ہیں کہ وہ ایک بالکل مختلف فنکشن، _f_ کو کال کرتے ہیں۔
اصلی _f_ فنکشن
function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
if (sender == contract_owner){
sender = deployer;
}
emit Transfer(sender, recipient, amount);
}
اس فنکشن میں دو ممکنہ خطرے کی نشانیاں (red flags) ہیں۔
-
فنکشن موڈیفائر (opens in a new tab)
_mod_کا استعمال۔ تاہم، جب ہم سورس کوڈ میں دیکھتے ہیں تو ہمیں معلوم ہوتا ہے کہ_mod_دراصل بے ضرر ہے۔modifier _mod_(address sender, address recipient, uint256 amount){ _; } -
وہی مسئلہ جو ہم نے
_transferمیں دیکھا تھا، جو یہ ہے کہ جبcontract_ownerٹوکن بھیجتا ہے تو وہdeployerسے آتے ہوئے دکھائی دیتے ہیں۔
جعلی ایونٹس فنکشن dropNewTokens
اب ہم ایک ایسی چیز کی طرف آتے ہیں جو ایک حقیقی سکیم کی طرح لگتی ہے۔ میں نے پڑھنے میں آسانی کے لیے فنکشن میں تھوڑی ترمیم کی ہے، لیکن یہ عملی طور پر یکساں ہے۔
function dropNewTokens(address uPool,
address[] memory eReceiver,
uint256[] memory eAmounts) public auth()
اس فنکشن میں auth() موڈیفائر ہے، جس کا مطلب ہے کہ اسے صرف کنٹریکٹ کا مالک ہی کال کر سکتا ہے۔
modifier auth() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
یہ پابندی بالکل معقول ہے، کیونکہ ہم نہیں چاہیں گے کہ بے ترتیب اکاؤنٹس ٹوکن تقسیم کریں۔ تاہم، باقی فنکشن مشکوک ہے۔
{
for (uint256 i = 0; i < eReceiver.length; i++) {
emit Transfer(uPool, eReceiver[i], eAmounts[i]);
}
}
ایک پول اکاؤنٹ سے وصول کنندگان کی ایک صف (array) کو رقوم کی ایک صف منتقل کرنے کا فنکشن بالکل معقول ہے۔ بہت سے ایسے استعمال کے معاملات ہیں جن میں آپ ایک ہی ذریعہ سے متعدد مقامات پر ٹوکن تقسیم کرنا چاہیں گے، جیسے پے رول، ایئر ڈراپس وغیرہ۔ متعدد ٹرانزیکشنز جاری کرنے، یا یہاں تک کہ ایک ہی ٹرانزیکشن کے حصے کے طور پر کسی مختلف کنٹریکٹ سے ERC-20 کو متعدد بار کال کرنے کے بجائے اسے ایک ہی ٹرانزیکشن میں کرنا (گیس کے لحاظ سے) سستا ہے۔
تاہم، dropNewTokens ایسا نہیں کرتا۔ یہ Transfer ایونٹس (opens in a new tab) خارج کرتا ہے، لیکن دراصل کوئی ٹوکن منتقل نہیں کرتا۔ آف چین ایپلی کیشنز کو ایسی منتقلی کے بارے میں بتا کر الجھانے کی کوئی جائز وجہ نہیں ہے جو حقیقت میں ہوئی ہی نہیں۔
جلانے والا Approve فنکشن
ERC-20 کنٹریکٹس میں الاؤنسز کے لیے ایک approve فنکشن ہونا چاہیے، اور واقعی ہمارے سکیم ٹوکن میں ایسا فنکشن موجود ہے، اور یہ درست بھی ہے۔ تاہم، چونکہ Solidity سی (C) سے ماخوذ ہے، اس لیے یہ کیس حساس (case significant) ہے۔ "Approve" اور "approve" مختلف سٹرنگز ہیں۔
اس کے علاوہ، اس کی فعالیت کا approve سے کوئی تعلق نہیں ہے۔
function Approve(
address[] memory holders)
اس فنکشن کو ٹوکن کے حاملین کے پتوں کی ایک صف (array) کے ساتھ کال کیا جاتا ہے۔
public approver() {
approver() موڈیفائر اس بات کو یقینی بناتا ہے کہ صرف contract_owner کو اس فنکشن کو کال کرنے کی اجازت ہے (نیچے دیکھیں)۔
for (uint256 i = 0; i < holders.length; i++) {
uint256 amount = _balances[holders[i]];
_beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount);
_balances[holders[i]] = _balances[holders[i]].sub(amount,
"ERC20: burn amount exceeds balance");
_balances[0x0000000000000000000000000000000000000001] =
_balances[0x0000000000000000000000000000000000000001].add(amount);
}
}
ہر ہولڈر کے پتے کے لیے، یہ فنکشن ہولڈر کا پورا بیلنس 0x00...01 پتے پر منتقل کر دیتا ہے، جو مؤثر طریقے سے اسے جلا دیتا ہے (معیار میں اصل burn کل سپلائی کو بھی تبدیل کرتا ہے، اور ٹوکنز کو 0x00...00 پر منتقل کرتا ہے)۔ اس کا مطلب ہے کہ contract_owner کسی بھی صارف کے اثاثے ہٹا سکتا ہے۔ یہ کوئی ایسی خصوصیت نہیں لگتی جو آپ کسی گورننس ٹوکن میں چاہیں گے۔
کوڈ کے معیار کے مسائل
کوڈ کے معیار کے یہ مسائل یہ ثابت نہیں کرتے کہ یہ کوڈ ایک سکیم ہے، لیکن یہ اسے مشکوک بناتے ہیں۔ آربٹرم جیسی منظم کمپنیاں عام طور پر اتنا برا کوڈ جاری نہیں کرتیں۔
mount فنکشن
اگرچہ یہ معیار (opens in a new tab) میں متعین نہیں ہے، لیکن عام طور پر نئے ٹوکن بنانے والے فنکشن کو mint کہا جاتا ہے۔
اگر ہم wARB کنسٹرکٹر میں دیکھیں، تو ہم دیکھتے ہیں کہ ٹائم منٹ فنکشن کا نام کسی وجہ سے mount رکھ دیا گیا ہے، اور کارکردگی کے لیے پوری رقم کے لیے ایک بار کال کرنے کے بجائے، اسے ابتدائی سپلائی کے پانچویں حصے کے ساتھ پانچ بار کال کیا گیا ہے۔
constructor () public {
_name = "Wrapped Arbitrum";
_symbol = "wARB";
_decimals = 18;
uint256 initialSupply = 1000000000000;
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
mount(deployer, initialSupply*(10**18)/5);
}
خود mount فنکشن بھی مشکوک ہے۔
function mount(address account, uint256 amount) public {
require(msg.sender == contract_owner, "ERC20: mint to the zero address");
require کو دیکھتے ہوئے، ہم دیکھتے ہیں کہ صرف کنٹریکٹ کے مالک کو ڈھالنے (mint) کی اجازت ہے۔ یہ جائز ہے۔ لیکن ایرر میسج only owner is allowed to mint یا اس جیسا کچھ ہونا چاہیے۔ اس کے بجائے، یہ غیر متعلقہ ERC20: mint to the zero address ہے۔ صفر ایڈریس پر ڈھالنے کا درست ٹیسٹ require(account != address(0), "<error message>") ہے، جسے کنٹریکٹ کبھی چیک کرنے کی زحمت نہیں کرتا۔
_totalSupply = _totalSupply.add(amount);
_balances[contract_owner] = _balances[contract_owner].add(amount);
emit Transfer(address(0), account, amount);
}
ڈھلائی سے براہ راست متعلق دو اور مشکوک حقائق ہیں:
-
ایک
accountپیرامیٹر ہے، جو غالباً وہ اکاؤنٹ ہے جسے ڈھالی گئی رقم وصول کرنی چاہیے۔ لیکن جو بیلنس بڑھتا ہے وہ دراصلcontract_ownerکا ہوتا ہے۔ -
اگرچہ بڑھا ہوا بیلنس
contract_ownerکا ہے، لیکن خارج ہونے والا ایونٹaccountکو منتقلی دکھاتا ہے۔
auth اور approver دونوں کیوں؟ mod کیوں جو کچھ نہیں کرتا؟
اس کنٹریکٹ میں تین موڈیفائرز شامل ہیں: _mod_، auth، اور approver۔
modifier _mod_(address sender, address recipient, uint256 amount){
_;
}
_mod_ تین پیرامیٹرز لیتا ہے اور ان کے ساتھ کچھ نہیں کرتا۔ اسے کیوں رکھا گیا ہے؟
modifier auth() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
modifier approver() {
require(msg.sender == contract_owner, "Not allowed to interact");
_;
}
auth اور approver زیادہ معقول لگتے ہیں، کیونکہ وہ چیک کرتے ہیں کہ کنٹریکٹ کو contract_owner نے کال کیا تھا۔ ہم توقع کریں گے کہ کچھ مراعات یافتہ کارروائیاں، جیسے ڈھلائی، اس اکاؤنٹ تک محدود ہوں۔ تاہم، دو الگ الگ فنکشنز رکھنے کا کیا فائدہ جو بالکل ایک ہی کام کرتے ہیں؟
ہم خودکار طریقے سے کیا پہچان سکتے ہیں؟
ہم Etherscan کو دیکھ کر جان سکتے ہیں کہ wARB ایک سکیم ٹوکن ہے۔ تاہم، یہ ایک مرکزی حل ہے۔ نظریاتی طور پر، Etherscan کو سبوتاژ یا ہیک کیا جا سکتا ہے۔ یہ بہتر ہے کہ آزادانہ طور پر یہ معلوم کیا جا سکے کہ آیا کوئی ٹوکن جائز ہے یا نہیں۔
کچھ ایسی چالیں ہیں جنہیں ہم یہ پہچاننے کے لیے استعمال کر سکتے ہیں کہ آیا کوئی ERC-20 ٹوکن مشکوک ہے (یا تو سکیم ہے یا بہت بری طرح لکھا گیا ہے)، ان کے خارج کردہ ایونٹس کو دیکھ کر۔
مشکوک Approval ایونٹس
Approval ایونٹس (opens in a new tab) صرف براہ راست درخواست کے ساتھ ہونے چاہئیں (اس کے برعکس Transfer ایونٹس (opens in a new tab) جو الاؤنس کے نتیجے میں ہو سکتے ہیں)۔ اس مسئلے کی تفصیلی وضاحت اور یہ جاننے کے لیے کہ درخواستیں کسی کنٹریکٹ کے ذریعے ثالثی کے بجائے براہ راست کیوں ہونی چاہئیں، Solidity کی دستاویزات دیکھیں (opens in a new tab)۔
اس کا مطلب ہے کہ Approval ایونٹس جو کسی بیرونی ملکیت والے اکاؤنٹ سے خرچ کرنے کی منظوری دیتے ہیں، انہیں ان ٹرانزیکشنز سے آنا چاہیے جو اس اکاؤنٹ سے شروع ہوتی ہیں، اور جن کی منزل ERC-20 کنٹریکٹ ہے۔ بیرونی ملکیت والے اکاؤنٹ سے کسی بھی دوسری قسم کی منظوری مشکوک ہے۔
یہاں ایک پروگرام ہے جو اس قسم کے ایونٹ کی شناخت کرتا ہے (opens in a new tab)، جس میں Viem (opens in a new tab) اور TypeScript (opens in a new tab) کا استعمال کیا گیا ہے، جو ٹائپ سیفٹی کے ساتھ JavaScript کی ایک قسم ہے۔ اسے چلانے کے لیے:
.env.exampleکو.envمیں کاپی کریں۔- ایتھیریم مین نیٹ نوڈ کا URL فراہم کرنے کے لیے
.envمیں ترمیم کریں۔ - ضروری پیکجز انسٹال کرنے کے لیے
pnpm installچلائیں۔ - مشکوک منظوریوں کو تلاش کرنے کے لیے
pnpm susApprovalچلائیں۔
یہاں لائن بہ لائن وضاحت ہے:
import {
Address,
TransactionReceipt,
createPublicClient,
http,
parseAbiItem,
} from "viem"
import { mainnet } from "viem/chains"
viem سے ٹائپ کی تعریفیں، فنکشنز، اور چین کی تعریف امپورٹ کریں۔
import { config } from "dotenv"
config()
URL حاصل کرنے کے لیے .env پڑھیں۔
const client = createPublicClient({
chain: mainnet,
transport: http(process.env.URL),
})
ایک Viem کلائنٹ بنائیں۔ ہمیں صرف بلاک چین سے پڑھنے کی ضرورت ہے، اس لیے اس کلائنٹ کو نجی کلید کی ضرورت نہیں ہے۔
const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82"
const fromBlock = 16859812n
const toBlock = 16873372n
مشکوک ERC-20 کنٹریکٹ کا پتہ، اور وہ بلاکس جن کے اندر ہم ایونٹس تلاش کریں گے۔ نوڈ فراہم کنندگان عام طور پر ایونٹس پڑھنے کی ہماری صلاحیت کو محدود کرتے ہیں کیونکہ بینڈوتھ مہنگی ہو سکتی ہے۔ خوش قسمتی سے wARB اٹھارہ گھنٹے کی مدت تک استعمال میں نہیں تھا، اس لیے ہم تمام ایونٹس تلاش کر سکتے ہیں (کل صرف 13 تھے)۔
const approvalEvents = await client.getLogs({
address: testedAddress,
fromBlock,
toBlock,
event: parseAbiItem(
"event Approval(address indexed _owner, address indexed _spender, uint256 _value)"
),
})
یہ Viem سے ایونٹ کی معلومات مانگنے کا طریقہ ہے۔ جب ہم اسے فیلڈ کے ناموں سمیت ایونٹ کے درست دستخط فراہم کرتے ہیں، تو یہ ہمارے لیے ایونٹ کو پارس (parse) کرتا ہے۔
const isContract = async (addr: Address): boolean =>
await client.getBytecode({ address: addr })
ہمارا الگورتھم صرف بیرونی ملکیت والے اکاؤنٹس پر لاگو ہوتا ہے۔ اگر client.getBytecode کے ذریعے کوئی بائٹ کوڈ واپس کیا جاتا ہے تو اس کا مطلب ہے کہ یہ ایک کنٹریکٹ ہے اور ہمیں اسے چھوڑ دینا چاہیے۔
اگر آپ نے پہلے TypeScript استعمال نہیں کی ہے، تو فنکشن کی تعریف تھوڑی عجیب لگ سکتی ہے۔ ہم اسے صرف یہ نہیں بتاتے کہ پہلے (اور واحد) پیرامیٹر کا نام addr ہے، بلکہ یہ بھی بتاتے ہیں کہ یہ Address ٹائپ کا ہے۔ اسی طرح، : boolean حصہ TypeScript کو بتاتا ہے کہ فنکشن کی واپسی کی قدر (return value) ایک بولین (boolean) ہے۔
const getEventTxn = async (ev: Event): TransactionReceipt =>
await client.getTransactionReceipt({ hash: ev.transactionHash })
یہ فنکشن کسی ایونٹ سے ٹرانزیکشن کی رسید حاصل کرتا ہے۔ ہمیں رسید کی ضرورت ہے تاکہ یہ یقینی بنایا جا سکے کہ ہمیں معلوم ہے کہ ٹرانزیکشن کی منزل کیا تھی۔
const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => {
یہ سب سے اہم فنکشن ہے، جو دراصل یہ فیصلہ کرتا ہے کہ آیا کوئی ایونٹ مشکوک ہے یا نہیں۔ واپسی کی ٹائپ، (Event | null)، TypeScript کو بتاتی ہے کہ یہ فنکشن یا تو Event یا null واپس کر سکتا ہے۔ اگر ایونٹ مشکوک نہیں ہے تو ہم null واپس کرتے ہیں۔
const owner = ev.args._owner
Viem کے پاس فیلڈ کے نام ہیں، اس لیے اس نے ہمارے لیے ایونٹ کو پارس کیا۔ _owner خرچ کیے جانے والے ٹوکنز کا مالک ہے۔
// کنٹریکٹس کی جانب سے منظوریاں مشکوک نہیں ہوتیں
if (await isContract(owner)) return null
اگر مالک ایک کنٹریکٹ ہے، تو فرض کریں کہ یہ منظوری مشکوک نہیں ہے۔ یہ چیک کرنے کے لیے کہ آیا کسی کنٹریکٹ کی منظوری مشکوک ہے یا نہیں، ہمیں ٹرانزیکشن کے مکمل نفاذ کو ٹریس کرنا ہوگا تاکہ یہ دیکھا جا سکے کہ آیا یہ کبھی مالک کے کنٹریکٹ تک پہنچی، اور کیا اس کنٹریکٹ نے براہ راست ERC-20 کنٹریکٹ کو کال کیا۔ یہ اس سے کہیں زیادہ وسائل خرچ کرنے والا کام ہے جتنا ہم کرنا چاہیں گے۔
const txn = await getEventTxn(ev)
اگر منظوری کسی بیرونی ملکیت والے اکاؤنٹ سے آتی ہے، تو وہ ٹرانزیکشن حاصل کریں جس کی وجہ سے یہ ہوئی۔
// منظوری مشکوک ہے اگر یہ کسی ایسے EOA مالک کی طرف سے آئے جو ٹرانزیکشن کا `from` نہیں ہے
if (owner.toLowerCase() != txn.from.toLowerCase()) return ev
ہم صرف سٹرنگ کی برابری کی جانچ نہیں کر سکتے کیونکہ پتے ہیکسا ڈیسیمل (hexadecimal) ہوتے ہیں، اس لیے ان میں حروف شامل ہوتے ہیں۔ بعض اوقات، مثال کے طور پر txn.from میں، وہ حروف تمام چھوٹے (lowercase) ہوتے ہیں۔ دیگر صورتوں میں، جیسے ev.args._owner، پتہ ایرر کی شناخت کے لیے مخلوط کیس (mixed-case) (opens in a new tab) میں ہوتا ہے۔
لیکن اگر ٹرانزیکشن مالک کی طرف سے نہیں ہے، اور وہ مالک بیرونی ملکیت والا ہے، تو ہمارے پاس ایک مشکوک ٹرانزیکشن ہے۔
// یہ بھی مشکوک ہے اگر ٹرانزیکشن کی منزل وہ ERC-20 کنٹریکٹ نہیں ہے جس کی ہم
// تحقیق کر رہے ہیں
if (txn.to.toLowerCase() != testedAddress) return ev
اسی طرح، اگر ٹرانزیکشن کا to پتہ، یعنی پہلا کال کیا گیا کنٹریکٹ، زیر تفتیش ERC-20 کنٹریکٹ نہیں ہے تو یہ مشکوک ہے۔
// اگر مشکوک ہونے کی کوئی وجہ نہیں ہے، تو null واپس کریں۔
return null
}
اگر دونوں میں سے کوئی بھی شرط درست نہیں ہے تو Approval ایونٹ مشکوک نہیں ہے۔
const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev))
const testResults = (await Promise.all(testPromises)).filter((x) => x != null)
console.log(testResults)
ایک async فنکشن (opens in a new tab) ایک Promise آبجیکٹ واپس کرتا ہے۔ عام نحو (syntax)، await x() کے ساتھ، ہم پروسیسنگ جاری رکھنے سے پہلے اس Promise کے پورا ہونے کا انتظار کرتے ہیں۔ اسے پروگرام کرنا اور اس پر عمل کرنا آسان ہے، لیکن یہ غیر موثر بھی ہے۔ جب ہم کسی مخصوص ایونٹ کے لیے Promise کے پورا ہونے کا انتظار کر رہے ہوتے ہیں تو ہم پہلے ہی اگلے ایونٹ پر کام شروع کر سکتے ہیں۔
یہاں ہم Promise آبجیکٹس کی ایک صف (array) بنانے کے لیے map (opens in a new tab) کا استعمال کرتے ہیں۔ پھر ہم ان تمام وعدوں (promises) کے حل ہونے کا انتظار کرنے کے لیے Promise.all (opens in a new tab) کا استعمال کرتے ہیں۔ اس کے بعد ہم غیر مشکوک ایونٹس کو ہٹانے کے لیے ان نتائج کو filter (opens in a new tab) کرتے ہیں۔
مشکوک Transfer ایونٹس
سکیم ٹوکنز کی شناخت کا ایک اور ممکنہ طریقہ یہ دیکھنا ہے کہ آیا ان میں کوئی مشکوک منتقلی ہوئی ہے۔ مثال کے طور پر، ان اکاؤنٹس سے منتقلی جن کے پاس اتنے ٹوکنز نہیں ہیں۔ آپ دیکھ سکتے ہیں کہ اس ٹیسٹ کو کیسے نافذ کیا جائے (opens in a new tab)، لیکن wARB میں یہ مسئلہ نہیں ہے۔
نتیجہ
ERC-20 سکیمز کی خودکار شناخت غلط منفی (false negatives) (opens in a new tab) کا شکار ہوتی ہے، کیونکہ ایک سکیم بالکل نارمل ERC-20 ٹوکن کنٹریکٹ استعمال کر سکتا ہے جو محض کسی حقیقی چیز کی نمائندگی نہیں کرتا۔ اس لیے آپ کو ہمیشہ کسی قابل اعتماد ذریعہ سے ٹوکن کا پتہ حاصل کرنے کی کوشش کرنی چاہیے۔
خودکار شناخت کچھ معاملات میں مدد کر سکتی ہے، جیسے کہ غیر مرکزی مالیات (DeFi) کے حصوں میں، جہاں بہت سے ٹوکنز ہوتے ہیں اور انہیں خودکار طریقے سے سنبھالنے کی ضرورت ہوتی ہے۔ لیکن ہمیشہ کی طرح خریدار ہوشیار رہے (caveat emptor) (opens in a new tab)، اپنی تحقیق خود کریں، اور اپنے صارفین کو بھی ایسا کرنے کی ترغیب دیں۔
میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔
صفحہ کی آخری اپ ڈیٹ: ۳ اپریل، ۲۰۲۶