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