मुख्य सामग्री पर जाएँ

स्कैम टोकन द्वारा उपयोग की जाने वाली कुछ तरकीबें और उन्हें कैसे पहचानें

स्कैम
Solidity
erc-20
JavaScript
TypeScript
माध्यमिक
ओरी पोमेरेंट्ज़
15 सितंबर 2023
17 मिनट का पठन

इस ट्यूटोरियल में हम एक स्कैम टोकन (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) उन टोकनों में से एक है जिनका सोर्स कोड उपलब्ध है, जिससे इसे समझना आसान हो जाता है।

जबकि अनुबंध डिप्लॉयर्स यह चुन सकते हैं कि सोर्स कोड प्रकाशित करना है या नहीं, वे गलत सोर्स कोड प्रकाशित नहीं कर सकते। ब्लॉक एक्सप्लोरर प्रदान किए गए सोर्स कोड को स्वतंत्र रूप से संकलित करता है, और यदि उसे बिल्कुल वही बाइटकोड नहीं मिलता है, तो वह उस सोर्स कोड को अस्वीकार कर देता है। आप इस बारे में ईथरस्कैन साइट पर और अधिक पढ़ सकते हैं (opens in a new tab)

वैध ERC-20 टोकन के साथ तुलना

हम इस टोकन की तुलना वैध ERC-20 टोकन से करने जा रहे हैं। यदि आप इस बात से परिचित नहीं हैं कि वैध ERC-20 टोकन आमतौर पर कैसे लिखे जाते हैं, तो यह ट्यूटोरियल देखें

विशेषाधिकार प्राप्त पतों के लिए स्थिरांक

अनुबंधों को कभी-कभी विशेषाधिकार प्राप्त पतों की आवश्यकता होती है। दीर्घकालिक उपयोग के लिए डिज़ाइन किए गए अनुबंध कुछ विशेषाधिकार प्राप्त पतों को उन पतों को बदलने की अनुमति देते हैं, उदाहरण के लिए एक नए मल्टीसिग अनुबंध के उपयोग को सक्षम करने के लिए। ऐसा करने के कई तरीके हैं।

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 */
4 function _setAdmin(address newAdmin) private {
5 require(newAdmin != address(0), "ERC1967: new admin is the zero address");
6 StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
7 }

इसके विपरीत, 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) एक अनुबंध नहीं है जिसे अलग-अलग समय पर अलग-अलग खातों द्वारा नियंत्रित किया जा सकता है, बल्कि एक बाह्य रूप से स्वामित्व वाला खाता है। इसका मतलब है कि यह शायद किसी व्यक्ति द्वारा अल्पकालिक उपयोग के लिए डिज़ाइन किया गया है, न कि एक ERC-20 को नियंत्रित करने के लिए एक दीर्घकालिक समाधान के रूप में जो मूल्यवान बना रहेगा।

और वास्तव में, अगर हम ईथरस्कैन में देखें तो हम देखते हैं कि घोटालेबाज ने 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");
4
5 _beforeTokenTransfer(sender, recipient, amount);
6
7 _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 }
5
6 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");
4
5 _beforeTokenTransfer(sender, recipient, amount);
6
7 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
8 _balances[recipient] = _balances[recipient].add(amount);
9 if (sender == contract_owner){
10
11 sender = deployer;
12 }
13 emit Transfer(sender, recipient, amount);
14 }

इस फ़ंक्शन में दो संभावित खतरे के संकेत हैं।

  • फ़ंक्शन मॉडिफ़ायर (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}

एक पूल खाते से रिसीवरों की एक सरणी को राशियों की एक सरणी में स्थानांतरित करने का एक फ़ंक्शन पूरी तरह से समझ में आता है। ऐसे कई उपयोग के मामले हैं जिनमें आप टोकन को एक ही स्रोत से कई गंतव्यों में वितरित करना चाहेंगे, जैसे कि पेरोल, एयरड्रॉप, आदि। कई लेनदेन जारी करने के बजाय, या एक ही लेनदेन के हिस्से के रूप में एक अलग अनुबंध से ERC-20 को कई बार कॉल करने के बजाय, इसे एक ही लेनदेन में करना (गैस में) सस्ता है।

हालांकि, dropNewTokens ऐसा नहीं करता है। यह Transfer इवेंट (opens in a new tab) उत्सर्जित करता है, लेकिन वास्तव में किसी भी टोकन को स्थानांतरित नहीं करता है। ऑफ़-चेन एप्लिकेशन को एक ऐसे हस्तांतरण के बारे में बताकर भ्रमित करने का कोई वैध कारण नहीं है जो वास्तव में हुआ ही नहीं।

जलाने वाला Approve फ़ंक्शन

ERC-20 अनुबंधों में भत्तों के लिए एक approve फ़ंक्शन होना चाहिए, और वास्तव में हमारे स्कैम टोकन में ऐसा फ़ंक्शन है, और यह सही भी है। हालांकि, क्योंकि सॉलिडिटी C से उतरा है, यह केस महत्वपूर्ण है। "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 कुल आपूर्ति को भी बदलता है, और टोकन को 0x00...00 पर स्थानांतरित करता है)। इसका मतलब है कि contract_owner किसी भी उपयोगकर्ता की संपत्ति को हटा सकता है। यह एक ऐसी विशेषता नहीं लगती है जो आप एक शासन टोकन में चाहेंगे।

कोड गुणवत्ता के मुद्दे

ये कोड गुणवत्ता के मुद्दे यह साबित नहीं करते हैं कि यह कोड एक स्कैम है, लेकिन वे इसे संदिग्ध बनाते हैं। आर्बिट्रम जैसी संगठित कंपनियाँ आमतौर पर इतना खराब कोड जारी नहीं करती हैं।

mount फ़ंक्शन

हालांकि यह मानक (opens in a new tab) में निर्दिष्ट नहीं है, आम तौर पर नए टोकन बनाने वाले फ़ंक्शन को mint (opens in a new tab) कहा जाता है।

यदि हम wARB कंस्ट्रक्टर में देखें, तो हम देखते हैं कि मिंट फ़ंक्शन का नाम बदलकर किसी कारण से mount कर दिया गया है, और दक्षता के लिए पूरी राशि के बजाय प्रारंभिक आपूर्ति के पांचवें हिस्से के साथ पांच बार कॉल किया जाता है।

1 constructor () public {
2
3 _name = "Wrapped Arbitrum";
4 _symbol = "wARB";
5 _decimals = 18;
6 uint256 initialSupply = 1000000000000;
7
8 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 को देखते हुए, हम देखते हैं कि केवल अनुबंध स्वामी को मिंट करने की अनुमति है। यह वैध है। लेकिन त्रुटि संदेश केवल स्वामी को मिंट करने की अनुमति है या ऐसा ही कुछ होना चाहिए। इसके बजाय, यह अप्रासंगिक ERC20: मिंट टू द ज़ीरो एड्रेस है। ज़ीरो एड्रेस पर मिंट करने के लिए सही परीक्षण 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 }
5
6 modifier approver() {
7 require(msg.sender == contract_owner, "Not allowed to interact");
8 _;
9 }

auth और approver अधिक समझ में आते हैं, क्योंकि वे जांचते हैं कि अनुबंध को contract_owner द्वारा कॉल किया गया था। हम उम्मीद करेंगे कि कुछ विशेषाधिकार प्राप्त कार्य, जैसे कि मिंटिंग, उस खाते तक सीमित हों। हालांकि, दो अलग-अलग फ़ंक्शन होने का क्या मतलब है जो ठीक वही काम करते हैं?

हम स्वचालित रूप से क्या पता लगा सकते हैं?

हम ईथरस्कैन को देखकर देख सकते हैं कि wARB एक स्कैम टोकन है। हालांकि, यह एक केंद्रीकृत समाधान है। सिद्धांत रूप में, ईथरस्कैन को विकृत या हैक किया जा सकता है। स्वतंत्र रूप से यह पता लगाना बेहतर है कि कोई टोकन वैध है या नहीं।

कुछ तरकीबें हैं जिनका उपयोग हम यह पहचानने के लिए कर सकते हैं कि एक ERC-20 टोकन संदिग्ध है (या तो एक स्कैम या बहुत खराब लिखा गया है), उनके द्वारा उत्सर्जित इवेंट्स को देखकर।

संदिग्ध Approval इवेंट्स

Approval इवेंट (opens in a new tab) केवल एक सीधे अनुरोध के साथ होने चाहिए (Transfer इवेंट (opens in a new tab) के विपरीत जो एक भत्ते के परिणामस्वरूप हो सकते हैं)। इस मुद्दे की विस्तृत व्याख्या के लिए सॉलिडिटी डॉक्स देखें (opens in a new tab) और यह कि अनुरोधों को सीधे क्यों होना चाहिए, न कि किसी अनुबंध द्वारा मध्यस्थता के बजाय।

इसका मतलब है कि बाह्य रूप से स्वामित्व वाले खाते से खर्च को मंजूरी देने वाले Approval इवेंट उन लेनदेन से आने चाहिए जो उस खाते से उत्पन्न होते हैं, और जिनका गंतव्य ERC-20 अनुबंध है। बाह्य रूप से स्वामित्व वाले खाते से किसी भी अन्य प्रकार का अनुमोदन संदिग्ध है।

यहाँ इस तरह के इवेंट की पहचान करने वाला एक प्रोग्राम (opens in a new tab) है, जो viem (opens in a new tab) और टाइपस्क्रिप्ट (opens in a new tab), जो टाइप सुरक्षा वाला एक जावास्क्रिप्ट संस्करण है, का उपयोग करता है। इसे चलाने के लिए:

  1. .env.example को .env में कॉपी करें।
  2. एक एथेरियम मेननेट नोड के लिए URL प्रदान करने के लिए .env संपादित करें।
  3. आवश्यक पैकेज स्थापित करने के लिए pnpm install चलाएँ।
  4. संदिग्ध अनुमोदनों की तलाश के लिए 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})

एक वीएम क्लाइंट बनाएँ। हमें केवल ब्लॉकचेन से पढ़ने की आवश्यकता है, इसलिए इस क्लाइंट को निजी कुंजी की आवश्यकता नहीं है।

1const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82"
2const fromBlock = 16859812n
3const 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})

यह वीएम से इवेंट जानकारी मांगने का तरीका है। जब हम इसे सटीक इवेंट हस्ताक्षर प्रदान करते हैं, जिसमें फ़ील्ड नाम शामिल हैं, तो यह हमारे लिए इवेंट को पार्स करता है।

1const isContract = async (addr: Address): boolean =>
2 await client.getBytecode({ address: addr })

हमारा एल्गोरिथम केवल बाह्य रूप से स्वामित्व वाले खातों पर लागू होता है। यदि client.getBytecode द्वारा कोई बाइटकोड लौटाया जाता है तो इसका मतलब है कि यह एक अनुबंध है और हमें बस इसे छोड़ देना चाहिए।

यदि आपने पहले टाइपस्क्रिप्ट का उपयोग नहीं किया है, तो फ़ंक्शन परिभाषा थोड़ी अजीब लग सकती है। हम इसे केवल यह नहीं बताते कि पहला (और एकमात्र) पैरामीटर addr कहलाता है, बल्कि यह भी कि यह Address प्रकार का है। इसी तरह, : boolean भाग टाइपस्क्रिप्ट को बताता है कि फ़ंक्शन का वापसी मान एक बूलियन है।

1const getEventTxn = async (ev: Event): TransactionReceipt =>
2 await client.getTransactionReceipt({ hash: ev.transactionHash })

यह फ़ंक्शन एक इवेंट से लेनदेन की रसीद प्राप्त करता है। हमें यह सुनिश्चित करने के लिए रसीद की आवश्यकता है कि हम जानते हैं कि लेनदेन का गंतव्य क्या था।

1const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => {

यह सबसे महत्वपूर्ण फ़ंक्शन है, जो वास्तव में यह तय करता है कि कोई इवेंट संदिग्ध है या नहीं। वापसी प्रकार, (Event | null), टाइपस्क्रिप्ट को बताता है कि यह फ़ंक्शन या तो एक Event या null लौटा सकता है। यदि इवेंट संदिग्ध नहीं है तो हम null लौटाते हैं।

1const owner = ev.args._owner

वीएम में फ़ील्ड नाम हैं, इसलिए इसने हमारे लिए इवेंट को पार्स किया। _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

हम केवल स्ट्रिंग समानता की जांच नहीं कर सकते क्योंकि पते हेक्साडेसिमल होते हैं, इसलिए उनमें अक्षर होते हैं। कभी-कभी, उदाहरण के लिए txn.from में, वे सभी अक्षर लोअरकेस होते हैं। अन्य मामलों में, जैसे ev.args._owner, पता त्रुटि पहचान के लिए मिश्रित-केस (opens in a new tab) में है।

लेकिन अगर लेनदेन स्वामी से नहीं है, और वह स्वामी बाह्य रूप से स्वामित्व में है, तो हमारे पास एक संदिग्ध लेनदेन है।

1// यह भी संदिग्ध है यदि लेनदेन का गंतव्य वह ERC-20 अनुबंध नहीं है जिसकी हम
2// जांच कर रहे हैं
3if (txn.to.toLowerCase() != testedAddress) return ev

इसी तरह, यदि लेनदेन का to पता, पहला अनुबंध जिसे कॉल किया गया, जांच के तहत ERC-20 अनुबंध नहीं है तो यह संदिग्ध है।

1 // यदि संदिग्ध होने का कोई कारण नहीं है, तो null लौटाएं।
2 return null
3}

यदि कोई भी शर्त सही नहीं है तो Approval इवेंट संदिग्ध नहीं है।

1const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev))
2const testResults = (await Promise.all(testPromises)).filter((x) => x != null)
3
4console.log(testResults)

एक async फ़ंक्शन (opens in a new tab) एक Promise ऑब्जेक्ट लौटाता है। सामान्य सिंटैक्स, await x() के साथ, हम प्रसंस्करण जारी रखने से पहले उस Promise के पूरा होने की प्रतीक्षा करते हैं। यह प्रोग्राम करना और अनुसरण करना सरल है, लेकिन यह अक्षम भी है। जब हम किसी विशिष्ट इवेंट के लिए Promise के पूरा होने की प्रतीक्षा कर रहे होते हैं, तो हम पहले से ही अगले इवेंट पर काम करना शुरू कर सकते हैं।

यहाँ हम Promise ऑब्जेक्ट्स की एक सरणी बनाने के लिए map (opens in a new tab) का उपयोग करते हैं। फिर हम उन सभी वादों के हल होने की प्रतीक्षा करने के लिए Promise.all (opens in a new tab) का उपयोग करते हैं। फिर हम गैर-संदिग्ध इवेंट्स को हटाने के लिए उन परिणामों को filter (opens in a new tab) करते हैं।

संदिग्ध Transfer इवेंट्स

स्कैम टोकन की पहचान करने का एक और संभावित तरीका यह देखना है कि क्या उनके पास कोई संदिग्ध हस्तांतरण है। उदाहरण के लिए, उन खातों से हस्तांतरण जिनमें उतने टोकन नहीं हैं। आप देख सकते हैं कि इस परीक्षण को कैसे लागू किया जाए (opens in a new tab), लेकिन wARB में यह समस्या नहीं है।

निष्कर्ष

ERC-20 स्कैम का स्वचालित पता लगाना गलत नकारात्मक (opens in a new tab) से ग्रस्त है, क्योंकि एक स्कैम एक पूरी तरह से सामान्य ERC-20 टोकन अनुबंध का उपयोग कर सकता है जो बस कुछ भी वास्तविक का प्रतिनिधित्व नहीं करता है। इसलिए आपको हमेशा एक विश्वसनीय स्रोत से टोकन पता प्राप्त करने का प्रयास करना चाहिए।

स्वचालित पता लगाना कुछ मामलों में मदद कर सकता है, जैसे कि डीफाई टुकड़े, जहां कई टोकन होते हैं और उन्हें स्वचालित रूप से संभालने की आवश्यकता होती है। लेकिन हमेशा की तरह केविएट एम्प्टर (opens in a new tab), अपना खुद का शोध करें, और अपने उपयोगकर्ताओं को भी ऐसा करने के लिए प्रोत्साहित करें।

मेरे और काम के लिए यहाँ देखें (opens in a new tab)

पेज का अंतिम अपडेट: 3 मार्च 2026

क्या यह ट्यूटोरियल सहायक था?