بعض الحيل التي تستخدمها الرموز المميزة الاحتيالية وكيفية اكتشافها
في هذا البرنامج التعليمي، نقوم بتشريح رمز مميز احتيالي (يفتح في علامة تبويب جديدة) لرؤية بعض الحيل التي يلعبها المحتالون وكيفية تنفيذهم لها. بنهاية هذا البرنامج التعليمي، سيكون لديك نظرة أكثر شمولاً حول عقود الرموز المميزة ERC-20، وقدراتها، ولماذا يعد التشكيك ضروريًا. ثم ننظر إلى الأحداث التي يصدرها هذا الرمز المميز الاحتيالي ونرى كيف يمكننا التعرف تلقائيًا على أنه غير شرعي.
الرموز المميزة الاحتيالية - ما هي، ولماذا يقوم الناس بإنشائها، وكيفية تجنبها
أحد الاستخدامات الأكثر شيوعًا لشبكة إيثيريوم هو قيام مجموعة بإنشاء رمز مميز قابل للتداول، وبمعنى آخر عملتهم الخاصة. ومع ذلك، في أي مكان توجد فيه حالات استخدام شرعية تجلب قيمة، يوجد أيضًا مجرمون يحاولون سرقة تلك القيمة لأنفسهم.
يمكنك قراءة المزيد حول هذا الموضوع في مكان آخر على موقع ethereum.org من منظور المستخدم. يركز هذا البرنامج التعليمي على تشريح رمز مميز احتيالي لمعرفة كيف يتم ذلك وكيف يمكن اكتشافه.
كيف أعرف أن wARB هو احتيال؟
الرمز المميز الذي نقوم بتشريحه هو wARB (يفتح في علامة تبويب جديدة)، والذي يتظاهر بأنه مكافئ لـ الرمز المميز ARB (يفتح في علامة تبويب جديدة) الشرعي.
أسهل طريقة لمعرفة أي الرموز المميزة هو الشرعي هي النظر إلى المنظمة الأصلية، أربيتروم (يفتح في علامة تبويب جديدة). العناوين الشرعية محددة في وثائقهم (يفتح في علامة تبويب جديدة).
لماذا يتوفر الكود المصدري؟
عادةً ما نتوقع من الأشخاص الذين يحاولون الاحتيال على الآخرين أن يكونوا متكتمين، وبالفعل العديد من الرموز المميزة الاحتيالية لا يتوفر الكود الخاص بها (على سبيل المثال، هذا الرمز (يفتح في علامة تبويب جديدة) وهذا الرمز (يفتح في علامة تبويب جديدة)).
ومع ذلك، عادةً ما تنشر الرموز المميزة الشرعية الكود المصدري الخاص بها، لذلك للظهور بمظهر شرعي، يقوم مؤلفو الرموز المميزة الاحتيالية أحيانًا بنفس الشيء. wARB (يفتح في علامة تبويب جديدة) هو أحد تلك الرموز المميزة التي يتوفر الكود المصدري الخاص بها، مما يسهل فهمه.
بينما يمكن لناشري العقود اختيار ما إذا كانوا سينشرون الكود المصدري أم لا، إلا أنهم لا يمكنهم نشر كود مصدري خاطئ. يقوم مستكشف الكتل بتجميع الكود المصدري المقدم بشكل مستقل، وإذا لم يحصل على نفس رمز البايت بالضبط، فإنه يرفض ذلك الكود المصدري. يمكنك قراءة المزيد حول هذا على موقع Etherscan (يفتح في علامة تبويب جديدة).
مقارنة مع الرموز المميزة ERC-20 الشرعية
سنقوم بمقارنة هذا الرمز المميز بالرموز المميزة ERC-20 الشرعية. إذا لم تكن على دراية بكيفية كتابة الرموز المميزة ERC-20 الشرعية عادةً، راجع هذا البرنامج التعليمي.
الثوابت للعناوين ذات الامتيازات
تحتاج العقود أحيانًا إلى عناوين ذات امتيازات. تسمح العقود المصممة للاستخدام طويل الأمد لبعض العناوين ذات الامتيازات بتغيير تلك العناوين، على سبيل المثال لتمكين استخدام عقد متعدد التوقيعات جديد. هناك عدة طرق للقيام بذلك.
يستخدم عقد الرمز المميز HOP (يفتح في علامة تبويب جديدة) نمط Ownable (يفتح في علامة تبويب جديدة). يتم الاحتفاظ بالعنوان ذي الامتيازات في التخزين، في حقل يسمى _owner (انظر الملف الثالث، Ownable.sol).
abstract contract Ownable is Context {
address private _owner;
.
.
.
}
لا يحتوي عقد الرمز المميز ARB (يفتح في علامة تبويب جديدة) على عنوان ذي امتيازات بشكل مباشر. ومع ذلك، فإنه لا يحتاج إلى واحد. إنه يقع خلف proxy (يفتح في علامة تبويب جديدة) في العنوان 0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 (يفتح في علامة تبويب جديدة). يحتوي ذلك العقد على عنوان ذي امتيازات (انظر الملف الرابع، 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;
.
.
.
}
مالك العقد هذا (يفتح في علامة تبويب جديدة) ليس عقدًا يمكن التحكم فيه بواسطة حسابات مختلفة في أوقات مختلفة، بل هو حساب مملوك خارجيًا. هذا يعني أنه ربما تم تصميمه للاستخدام قصير الأمد من قبل فرد، بدلاً من كونه حلاً طويل الأمد للتحكم في رمز ERC-20 سيظل ذا قيمة.
وبالفعل، إذا نظرنا في Etherscan نرى أن المحتال استخدم هذا العقد لمدة 12 ساعة فقط (المعاملة الأولى (يفتح في علامة تبويب جديدة) إلى المعاملة الأخيرة (يفتح في علامة تبويب جديدة)) خلال 19 مايو 2023.
الدالة _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);
}
هناك علامتان تحذيريتان محتملتان في هذه الدالة.
-
استخدام مُعدِّل الدالة (يفتح في علامة تبويب جديدة)
_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]);
}
}
وجود دالة للتحويل من حساب مجمع إلى مصفوفة من المستلمين بمصفوفة من المبالغ أمر منطقي تمامًا. هناك العديد من حالات الاستخدام التي سترغب فيها في توزيع الرموز المميزة من مصدر واحد إلى وجهات متعددة، مثل كشوف المرتبات، والتوزيعات المجانية (airdrops)، وما إلى ذلك. من الأرخص (من حيث الغاز) القيام بذلك في معاملة واحدة بدلاً من إصدار معاملات متعددة، أو حتى استدعاء ERC-20 عدة مرات من عقد مختلف كجزء من نفس المعاملة.
ومع ذلك، فإن dropNewTokens لا تفعل ذلك. إنها تصدر أحداث Transfer (يفتح في علامة تبويب جديدة)، لكنها لا تقوم فعليًا بتحويل أي رموز مميزة. لا يوجد سبب شرعي لإرباك التطبيقات خارج السلسلة بإخبارها عن تحويل لم يحدث في الواقع.
دالة الحرق Approve
من المفترض أن تحتوي عقود ERC-20 على دالة approve للسماحيات، وبالفعل يحتوي الرمز المميز الاحتيالي الخاص بنا على مثل هذه الدالة، وهي صحيحة أيضًا. ومع ذلك، نظرًا لأن لغة Solidity مشتقة من لغة C، فإن حالة الأحرف مهمة. "Approve" و "approve" هما سلسلتان نصيتان مختلفتان.
أيضًا، الوظيفة لا تتعلق بـ approve.
function Approve(
address[] memory holders)
يتم استدعاء هذه الدالة باستخدام مصفوفة من العناوين لحاملي الرمز المميز.
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
بينما لم يتم تحديد ذلك في المعيار (يفتح في علامة تبويب جديدة)، بشكل عام تُسمى الدالة التي تنشئ رموزًا مميزة جديدة 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، نرى أنه يُسمح لمالك العقد فقط بالسك. هذا أمر شرعي. لكن رسالة الخطأ يجب أن تكون يُسمح للمالك فقط بالسك أو شيء من هذا القبيل. بدلاً من ذلك، فهي الرسالة غير ذات الصلة 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. نتوقع أن تقتصر بعض الإجراءات ذات الامتيازات، مثل السك، على ذلك الحساب. ومع ذلك، ما الفائدة من وجود دالتين منفصلتين تفعلان نفس الشيء بالضبط؟
ما الذي يمكننا اكتشافه تلقائيًا؟
يمكننا أن نرى أن wARB هو رمز مميز احتيالي من خلال النظر إلى Etherscan. ومع ذلك، هذا حل مركزي. نظريًا، يمكن تخريب Etherscan أو اختراقه. من الأفضل أن نكون قادرين على معرفة ما إذا كان الرمز المميز شرعيًا أم لا بشكل مستقل.
هناك بعض الحيل التي يمكننا استخدامها لتحديد أن رمز ERC-20 مريب (إما أنه احتيال أو مكتوب بشكل سيء للغاية)، من خلال النظر إلى الأحداث التي يصدرها.
أحداث Approval المريبة
يجب أن تحدث أحداث Approval (يفتح في علامة تبويب جديدة) فقط بطلب مباشر (على عكس أحداث Transfer (يفتح في علامة تبويب جديدة) التي يمكن أن تحدث نتيجة لسماحية). راجع وثائق Solidity (يفتح في علامة تبويب جديدة) للحصول على شرح مفصل لهذه المشكلة ولماذا يجب أن تكون الطلبات مباشرة، بدلاً من أن تتم بوساطة عقد.
هذا يعني أن أحداث Approval التي توافق على الإنفاق من حساب مملوك خارجيًا يجب أن تأتي من معاملات تنشأ في ذلك الحساب، وتكون وجهتها هي عقد ERC-20. أي نوع آخر من الموافقة من حساب مملوك خارجيًا يُعد مريبًا.
إليك برنامج يحدد هذا النوع من الأحداث (يفتح في علامة تبويب جديدة)، باستخدام Viem (يفتح في علامة تبويب جديدة) وTypeScript (يفتح في علامة تبويب جديدة)، وهي نسخة من JavaScript تتميز بأمان الكتابة. لتشغيله:
- انسخ
.env.exampleإلى.env. - قم بتحرير
.envلتوفير عنوان URL لعقدة شبكة إيثيريوم الرئيسية. - قم بتشغيل
pnpm installلتثبيت الحزم اللازمة. - قم بتشغيل
pnpm susApprovalللبحث عن الموافقات المريبة.
إليك شرح سطرًا بسطر:
import {
Address,
TransactionReceipt,
createPublicClient,
http,
parseAbiItem,
} from "viem"
import { mainnet } from "viem/chains"
استيراد تعريفات الأنواع، والدوال، وتعريف السلسلة من viem.
import { config } from "dotenv"
config()
قراءة .env للحصول على عنوان URL.
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. عندما نزوده بتوقيع الحدث الدقيق، بما في ذلك أسماء الحقول، فإنه يقوم بتحليل الحدث لنا.
const isContract = async (addr: Address): boolean =>
await client.getBytecode({ address: addr })
الخوارزمية الخاصة بنا قابلة للتطبيق فقط على الحسابات المملوكة خارجيًا. إذا كان هناك أي رمز بايت يتم إرجاعه بواسطة client.getBytecode، فهذا يعني أن هذا عقد ويجب علينا تخطيه ببساطة.
إذا لم تكن قد استخدمت TypeScript من قبل، فقد يبدو تعريف الدالة غريبًا بعض الشيء. نحن لا نخبره فقط أن المعامل الأول (والوحيد) يسمى addr، ولكن أيضًا أنه من النوع Address. وبالمثل، يخبر الجزء : boolean TypeScript أن القيمة المرجعة للدالة هي قيمة منطقية (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
لا يمكننا فقط التحقق من تطابق السلاسل النصية لأن العناوين سداسية عشرية، لذا فهي تحتوي على أحرف. في بعض الأحيان، على سبيل المثال في txn.from، تكون تلك الأحرف كلها صغيرة. في حالات أخرى، مثل ev.args._owner، يكون العنوان بأحرف مختلطة لتحديد الأخطاء (يفتح في علامة تبويب جديدة).
ولكن إذا لم تكن المعاملة من المالك، وكان ذلك المالك مملوكًا خارجيًا، فلدينا معاملة مريبة.
// كما تكون مشبوهة إذا لم تكن وجهة المعاملة هي عقد 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 (يفتح في علامة تبويب جديدة) كائن Promise. باستخدام الصيغة الشائعة، await x()، ننتظر حتى يتم الوفاء بـ Promise قبل أن نواصل المعالجة. هذا بسيط في البرمجة والمتابعة، ولكنه غير فعال أيضًا. بينما ننتظر الوفاء بـ Promise لحدث معين، يمكننا بالفعل البدء في العمل على الحدث التالي.
هنا نستخدم map (يفتح في علامة تبويب جديدة) لإنشاء مصفوفة من كائنات Promise. ثم نستخدم Promise.all (يفتح في علامة تبويب جديدة) لانتظار حل جميع تلك الوعود (promises). ثم نقوم بـ filter (يفتح في علامة تبويب جديدة) تلك النتائج لإزالة الأحداث غير المريبة.
أحداث Transfer المريبة
طريقة أخرى محتملة لتحديد الرموز المميزة الاحتيالية هي معرفة ما إذا كان لديها أي تحويلات مريبة. على سبيل المثال، التحويلات من حسابات لا تمتلك هذا العدد الكبير من الرموز المميزة. يمكنك رؤية كيفية تنفيذ هذا الاختبار (يفتح في علامة تبويب جديدة)، لكن wARB لا يعاني من هذه المشكلة.
الخاتمة
يعاني الاكتشاف التلقائي لعمليات الاحتيال في ERC-20 من السلبيات الخاطئة (يفتح في علامة تبويب جديدة)، لأن عملية الاحتيال يمكن أن تستخدم عقد رمز مميز ERC-20 طبيعي تمامًا ولكنه لا يمثل أي شيء حقيقي. لذلك يجب عليك دائمًا محاولة الحصول على عنوان الرمز المميز من مصدر موثوق.
يمكن أن يساعد الاكتشاف التلقائي في حالات معينة، مثل أجزاء التمويل اللامركزي (DeFi)، حيث يوجد العديد من الرموز المميزة ويجب التعامل معها تلقائيًا. ولكن كما هو الحال دائمًا ليحذر المشتري (يفتح في علامة تبويب جديدة)، قم بإجراء بحثك الخاص، وشجع المستخدمين على القيام بالمثل.