> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# التمويل اللامركزي DeFi: صناديق السيولة ومخاطرها
- URL: https://arabroot.io/articles/التمويل-اللامركزي-defi-المنصات-الالية-وصناديق-السيولة-ومخاطرها/
- Published: 2026-10-28T06:09:35.000Z
- Updated: 2026-10-28T07:46:03.000Z
- Description: نبني صندوق سيولة وعقد إقراض بأيدينا ونرى بالأرقام أثر السعر والخسارة غير الدائمة وهجوم الساندويتش والتلاعب بالأسعار.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, البلوكتشين والعملات الرقمية, الاستضافة الذاتية

لعلك رأيت على موقع إحدى منصات التمويل اللامركزي على شبكة Ethereum رقم عائد سنوي يبدو أعلى بكثير من أي حساب بنكي، مكتوباً بجانب «صندوق سيولة» Liquidity Pool، وقرأت أن الفكرة بسيطة: تضع رمزين في الصندوق، فتأتي إليك رسوم التداول. وربما سمعت من يقترح أن يبني تطبيق ادخار على هذه الصناديق، فيضع فيها جزءاً من أرصدة العملاء ويقسم العائد معهم. والسؤالان اللذان يجب أن تسألهما أمام هذا الرقم: هل هذا ممكن تقنياً؟ وما المخاطر؟ والسؤال الأول جوابه نعم، فالكود متاح للجميع والتكامل معه ممكن في أيام. أما السؤال الثاني فهو موضوع هذا المقال، لأن العائد الذي على الموقع له مصادر محددة، ولكل مصدر ثمن، وأغلب هذا الثمن لا يظهر في رقم العائد.

والتمويل اللامركزي Decentralized Finance، واختصاراً DeFi، هو اسم يجمع تطبيقات مالية مبنية كعقود ذكية على شبكة عامة: تبادل الرموز، والإقراض، والعملات المستقرة، وغيرها، يستخدمها أي شخص بمحفظته دون حساب عند مؤسسة، كما تصف [صفحة DeFi في ethereum.org](https://ethereum.org/en/defi/?ref=arabroot.io). وسوف نبني في هذا المقال نسخة مصغرة من أهم لبنتين فيه بكودنا: صانع سوق آلياً بصندوق سيولة على طريقة Uniswap v2، وعقد إقراض يقرأ السعر من هذا الصندوق، ثم نشغلهما في اختبارات Foundry ونرى بالأرقام أثر السعر، والخسارة غير الدائمة، وهجوم الساندويتش، والتلاعب بمصدر الأسعار. والمقال تعليمي تقني فقط، ولا يوصي بأي منصة أو رمز، ولا يعد بأي عائد، وكل الرموز فيه رموز اختبار بلا قيمة.

وسوف نناقش في هذا المقال ما يلي:

- ما الذي يقدمه التمويل اللامركزي، ولماذا يختلف عن الحساب البنكي في كل شيء تقريباً.
- صانع السوق الآلي AMM ومعادلة حاصل الضرب الثابت، بعقد Solidity نكتبه بأنفسنا.
- أثر السعر Price Impact وحد الانزلاق Slippage، بأرقام من تجربة.
- مزودو السيولة: من أين يأتي العائد، والخسارة غير الدائمة Impermanent Loss بالحساب.
- ترتيب المعاملات والقيمة القابلة للاستخراج MEV، وهجوم الساندويتش بالتجربة.
- الإقراض ومصادر الأسعار Oracles، وتجربة تلاعب تقترض فيها 124,700 بضمان قيمته 10,000.
- خريطة المخاطر، والنظام في المملكة، وقائمة أسئلة لأي مقترح منتج من هذا النوع.

## ما الذي يقدمه التمويل اللامركزي فعلاً؟

في البنك، عندما تودع مالاً فأنت تقرضه للبنك، والبنك يقرضه لغيرك ويعطيك جزءاً من الفائدة، وكل ذلك داخل مؤسسة مرخصة ومراقبة، وقد شرحنا كيف يعمل البنك من الداخل في [مقال النظام المصرفي الأساسي](https://arabroot.io/articles/%D8%A7%D9%84%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%B5%D8%B1%D9%81%D9%8A-%D8%A7%D9%84%D8%A7%D8%B3%D8%A7%D8%B3%D9%8A-core-banking-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D8%A8%D9%86%D9%83-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84/). أما في التمويل اللامركزي فلا توجد مؤسسة: العقد الذكي نفسه يحفظ الأموال، ويطبق القواعد المكتوبة في كوده على كل من يتعامل معه بنفس الطريقة، ويستطيع أي شخص أن يقرأ الكود ويرى الأرصدة. وهذا يعطي خصائص لا يعطيها البنك: الخدمة متاحة لأي محفظة دون فتح حساب، والقواعد علنية ولا تتغير بقرار موظف، والتسوية فورية داخل المعاملة نفسها.

ولكن نفس هذه الخصائص هي مصدر المخاطر. فالعقد الذي يطبق القواعد على الجميع يطبقها أيضاً على من وجد ثغرة فيها، ولا يوجد قسم مخاطر يوقف معاملة مشبوهة، ولا يوجد ضمان للودائع، ولا توجد جهة تعيد المال إذا ضاع بسبب خطأ في الكود، كما رأينا في حادثة The DAO التي ذكرناها في المقال الأول وفي مقال تاريخ العملات الرقمية. لذلك السؤال الصحيح أمام رقم العائد ليس «هل العائد مرتفع؟» وإنما «من أين يأتي هذا العائد، ومن يتحمل الخسارة إذا حدث شيء؟»، وسوف نجيب عنه بأن نبني الآلة نفسها.

وهناك خاصية ثالثة يحب المطورون التعبير عنها بأن التمويل اللامركزي «قطع ليغو مالية»، وهي قابلية التركيب Composability: أي عقد يستطيع أن يستدعي أي عقد آخر في نفس المعاملة دون أن يطلب إذناً، فتستطيع أن تبني منتجاً جديداً من صندوق تبادل وعقد إقراض وعملة مستقرة كتبها آخرون، كما سنفعل في التجربة عندما يقرأ عقد الإقراض سعره من الصندوق. وهذه الخاصية هي ما جعل هذا المجال يتطور بسرعة، وهي أيضاً ما يجعل خطأ في قطعة واحدة ينتقل إلى كل ما بني فوقها، ويجعل المهاجم يستطيع أن يجمع عدة عقود في معاملة واحدة لا يستطيع أي منها وحده أن يرى الصورة كاملة.

## صانع السوق الآلي AMM: معادلة بدلاً من دفتر أوامر

المنصة التقليدية لتبادل الأصول تعمل بدفتر أوامر Order Book: بائعون يعرضون أسعارهم، ومشترون يطلبون، ومحرك مطابقة يجمع بينهم، وسوف نشرحه في مقال منصات التداول. وهذا يحتاج آلاف الأوامر تتغير كل ثانية، وكل تغيير على شبكة عامة معاملة برسوم، فلا يصلح أن يكتب كله على السلسلة. والحل الذي انتشر هو صانع السوق الآلي Automated Market Maker: بدلاً من مطابقة بائع بمشتر، يوجد صندوق Pool يحمل رصيدين من رمزين، ويستطيع أي شخص أن يشتري من الصندوق أو يبيع له بسعر تحدده معادلة.

والمعادلة الأشهر هي حاصل الضرب الثابت x · y = k، كما يشرحها [الورقة التقنية لـ Uniswap v2](https://app.uniswap.org/whitepaper.pdf?ref=arabroot.io): رصيد الرمز الأول ضرب رصيد الرمز الثاني يجب أن يبقى ثابتاً بعد كل عملية، مع رسوم 0.30% تضاف إلى الصندوق. فإذا أدخلت كمية من الرمز الأول، يحسب الصندوق كم يجب أن يخرج من الرمز الثاني حتى يبقى حاصل الضرب كما كان. والسعر هنا ليس رقماً يضعه أحد، وإنما نسبة الرصيدين: إذا كان في الصندوق 100,000 من رمز يشبه الدولار وسميناه USD، و100 من رمز أصل سميناه GOLD، فسعر GOLD الآني 1,000 USD. والصورة التالية تبين الفكرة:

![منحنى حاصل الضرب الثابت x ضرب y يساوي k، عليه نقطة البداية 100,000 USD و100 GOLD، ونقطة بعد شراء بـ 50,000 USD حيث يصبح الصندوق 150,000 USD و66.73 GOLD، وبجانبه ثلاث بطاقات: صندوق السيولة يودع فيه مزود السيولة الرمزين ويحصل على حصص، والسعر من الأرصدة أي رصيد USD مقسوماً على رصيد GOLD يساوي 1,000 في البداية، والرسوم 0.3% تبقى في الصندوق وهي دخل مزودي السيولة](https://arabroot.io/content/images/2026/10/defi-basics-01-amm.webp)

السعر في الصندوق نسبة رصيدين، وكل عملية تحرك النقطة على المنحنى

### لماذا لا يوضع دفتر الأوامر على السلسلة؟

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

### تجربة: نكتب الصندوق بأنفسنا

حتى نرى كل سطر، لن نستخدم مكتبة جاهزة، وإنما نكتب رمزاً بسيطاً على معيار ERC-20، وصندوقاً بحاصل ضرب ثابت، وعقد إقراض. والكود مكتوب للتعليم، ويترك عمداً حمايات موجودة في العقود الحقيقية مثل الحماية من إعادة الدخول Reentrancy والتعامل مع الرموز غير القياسية، فلا تنشره على أي شبكة حقيقية. أنشئ مشروع Foundry بمجلد `src`، واحفظ الملف الأول باسم `src/Token.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// A minimal ERC-20 for the lab (no OpenZeppelin, so every line is visible).
contract Token {
    string public name;
    string public symbol;
    uint8 public constant decimals = 18;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    constructor(string memory n, string memory s) { name = n; symbol = s; }

    function mint(address to, uint256 amt) external { balanceOf[to] += amt; totalSupply += amt; }

    function approve(address spender, uint256 amt) external returns (bool) {
        allowance[msg.sender][spender] = amt; return true;
    }
    function transfer(address to, uint256 amt) external returns (bool) {
        balanceOf[msg.sender] -= amt; balanceOf[to] += amt; return true;
    }
    function transferFrom(address from, address to, uint256 amt) external returns (bool) {
        allowance[from][msg.sender] -= amt; balanceOf[from] -= amt; balanceOf[to] += amt; return true;
    }
}
```

والرمز هنا جدول أرصدة داخل العقد، وأي شخص يستطيع أن يصك رموزاً بالدالة `mint` لأننا في تجربة. والدالتان `approve` و`transferFrom` هما آلية الموافقة التي ذكرناها في مقال المحافظ والمفاتيح: المالك يسمح لعقد آخر أن يسحب من رصيده حتى مبلغ معين. ثم الصندوق في الملف `src/Pool.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Token} from "./Token.sol";

// A constant-product pool (x * y = k) with a 0.3% fee, the idea behind Uniswap v2, reduced to its core.
contract Pool {
    Token public immutable a;   // e.g. a stablecoin-like token "USD"
    Token public immutable b;   // e.g. an asset token "GOLD"
    uint256 public ra;          // reserve of a
    uint256 public rb;          // reserve of b
    uint256 public shares;      // total liquidity shares
    mapping(address => uint256) public shareOf;

    constructor(Token _a, Token _b) { a = _a; b = _b; }

    function addLiquidity(uint256 amtA, uint256 amtB) external returns (uint256 s) {
        a.transferFrom(msg.sender, address(this), amtA);
        b.transferFrom(msg.sender, address(this), amtB);
        s = shares == 0 ? sqrt(amtA * amtB) : min(amtA * shares / ra, amtB * shares / rb);
        shares += s; shareOf[msg.sender] += s; ra += amtA; rb += amtB;
    }

    function removeLiquidity(uint256 s) external returns (uint256 outA, uint256 outB) {
        outA = s * ra / shares; outB = s * rb / shares;
        shareOf[msg.sender] -= s; shares -= s; ra -= outA; rb -= outB;
        a.transfer(msg.sender, outA); b.transfer(msg.sender, outB);
    }

    // amount out for a given amount in, after the 0.3% fee: out = rOut * in*997 / (rIn*1000 + in*997)
    function quote(uint256 amtIn, uint256 rIn, uint256 rOut) public pure returns (uint256) {
        uint256 inWithFee = amtIn * 997;
        return rOut * inWithFee / (rIn * 1000 + inWithFee);
    }

    function swapAforB(uint256 amtIn, uint256 minOut) external returns (uint256 out) {
        out = quote(amtIn, ra, rb);
        require(out >= minOut, "slippage: output below minimum");
        a.transferFrom(msg.sender, address(this), amtIn); b.transfer(msg.sender, out);
        ra += amtIn; rb -= out;
    }

    function swapBforA(uint256 amtIn, uint256 minOut) external returns (uint256 out) {
        out = quote(amtIn, rb, ra);
        require(out >= minOut, "slippage: output below minimum");
        b.transferFrom(msg.sender, address(this), amtIn); a.transfer(msg.sender, out);
        rb += amtIn; ra -= out;
    }

    // spot price of b in a (scaled by 1e18): what a naive app reads as "the price"
    function spotPriceB() external view returns (uint256) { return ra * 1e18 / rb; }

    function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; }
    }
    function min(uint256 x, uint256 y) internal pure returns (uint256) { return x < y ? x : y; }
}
```

في الكود أعلاه لاحظ التالي:

- الدالة `quote` هي قلب الصندوق: الكمية الداخلة بعد خصم 0.3% تضرب في 997 من 1000، ثم تحسب الكمية الخارجة بحيث يبقى حاصل الضرب ثابتاً، وهي نفس الصيغة التي تشرحها وثائق Uniswap v2.
- الدالتان `swapAforB` و`swapBforA` تأخذان معاملاً `minOut`، وهو أقل كمية يقبلها المستخدم، فإذا كانت الكمية المحسوبة أقل منه ترفض المعاملة كلها، وهذا هو حد الانزلاق الذي سنحتاجه في تجربة الساندويتش.
- الحصص `shares` تمثل نصيب كل مزود سيولة من الصندوق، وأول مزود يحصل على الجذر التربيعي لحاصل ضرب ما أودعه.
- الدالة `spotPriceB` تعطي السعر الآني، أي نسبة الرصيدين الآن، وهي الدالة التي سيستخدمها عقد الإقراض بطريقة خاطئة.

ثم عقد الإقراض في الملف `src/Lender.sol`، وفيه الخطأ الذي سنستغله عمداً:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Token} from "./Token.sol";
import {Pool} from "./Pool.sol";

// A lending desk with a fatal shortcut: it values collateral at the pool's spot price, right now.
contract Lender {
    Token public immutable usd;
    Token public immutable gold;
    Pool public immutable pool;
    mapping(address => uint256) public collateral;   // GOLD deposited
    mapping(address => uint256) public debt;         // USD borrowed

    constructor(Token _usd, Token _gold, Pool _pool) { usd = _usd; gold = _gold; pool = _pool; }

    function deposit(uint256 amt) external { gold.transferFrom(msg.sender, address(this), amt); collateral[msg.sender] += amt; }

    // borrow up to 50% of the collateral value (loan-to-value 50%)
    function borrow(uint256 amt) external {
        uint256 value = collateral[msg.sender] * pool.spotPriceB() / 1e18;
        require(debt[msg.sender] + amt <= value / 2, "not enough collateral");
        debt[msg.sender] += amt; usd.transfer(msg.sender, amt);
    }
}
```

العقد يقبل GOLD ضماناً، ويسمح باقتراض USD حتى 50% من قيمة الضمان، والقيمة يحسبها من السعر الآني في الصندوق في نفس اللحظة. والآن ملف الاختبار `test/DeFi.t.sol` الذي يبني الصندوق ويشغل خمسة سيناريوهات، ويستخدم مكتبة forge-std التي تثبتها بالأمر `forge install foundry-rs/forge-std --no-git`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {Token} from "../src/Token.sol";
import {Pool} from "../src/Pool.sol";
import {Lender} from "../src/Lender.sol";

contract DeFiLab is Test {
    Token usd; Token gold; Pool pool;
    address lp = address(0x11); address trader = address(0x22); address victim = address(0x33); address bot = address(0x44);

    function setUp() public {
        usd = new Token("Lab USD", "USD"); gold = new Token("Lab Gold", "GOLD");
        pool = new Pool(usd, gold);
        // the liquidity provider deposits 100,000 USD and 100 GOLD: the starting price is 1,000 USD per GOLD
        usd.mint(lp, 100_000e18); gold.mint(lp, 100e18);
        vm.startPrank(lp);
        usd.approve(address(pool), type(uint256).max); gold.approve(address(pool), type(uint256).max);
        pool.addLiquidity(100_000e18, 100e18);
        vm.stopPrank();
        for (uint i = 0; i < 3; i++) {
            address u = [trader, victim, bot][i];
            usd.mint(u, 1_000_000e18); gold.mint(u, 1_000e18);
            vm.startPrank(u); usd.approve(address(pool), type(uint256).max); gold.approve(address(pool), type(uint256).max); vm.stopPrank();
        }
    }

    function fmt(uint256 x) internal pure returns (string memory) {   // 18 decimals -> "1234.5678"
        uint256 w = x / 1e18; uint256 f = (x % 1e18) / 1e14;
        string memory fs = vm.toString(f);
        while (bytes(fs).length < 4) fs = string.concat("0", fs);
        return string.concat(vm.toString(w), ".", fs);
    }

    function test_1_priceImpact() public view {
        console2.log("spot price GOLD (USD)   :", fmt(pool.spotPriceB()));
        uint256[3] memory sizes = [uint256(1_000e18), 10_000e18, 50_000e18];
        for (uint i = 0; i < 3; i++) {
            uint256 out = pool.quote(sizes[i], pool.ra(), pool.rb());
            console2.log(string.concat("pay ", fmt(sizes[i]), " USD -> GOLD ", fmt(out), " | avg price ", fmt(sizes[i] * 1e18 / out)));
        }
    }

    function test_2_impermanentLoss() public {
        // outside traders push GOLD up: they buy GOLD until the pool price is about 4x
        vm.prank(trader); pool.swapAforB(100_000e18, 0);
        console2.log("spot price after trades:", fmt(pool.spotPriceB()));
        uint256 s = pool.shareOf(lp);
        vm.prank(lp); (uint256 outA, uint256 outB) = pool.removeLiquidity(s);
        uint256 p = outA * 1e18 / outB;                   // value GOLD at the new pool price
        uint256 lpValue = outA + outB * p / 1e18;
        uint256 holdValue = 100_000e18 + 100e18 * p / 1e18;
        console2.log("LP withdraws USD        :", fmt(outA));
        console2.log("LP withdraws GOLD       :", fmt(outB));
        console2.log("value if provided (USD) :", fmt(lpValue));
        console2.log("value if just held (USD):", fmt(holdValue));
        console2.log("difference % x100       :", (holdValue - lpValue) * 10000 / holdValue);
    }

    function test_3_sandwich() public {
        uint256 victimIn = 20_000e18;
        uint256 expected = pool.quote(victimIn, pool.ra(), pool.rb());
        uint256 minOut = expected * 95 / 100;                       // victim accepts 5% slippage
        console2.log("victim expects GOLD     :", fmt(expected));
        // the bot sees the pending swap and buys first (front-run)
        vm.prank(bot); uint256 botGold = pool.swapAforB(2_000e18, 0);
        vm.prank(victim); uint256 got = pool.swapAforB(victimIn, minOut);
        // then sells right after (back-run)
        vm.prank(bot); uint256 botUsd = pool.swapBforA(botGold, 0);
        console2.log("victim receives GOLD    :", fmt(got));
        console2.log("victim loss vs expected :", fmt(expected - got));
        console2.log("bot spent USD           :", fmt(2_000e18));
        console2.log("bot got back USD        :", fmt(botUsd));
    }

    function test_4_tightSlippageReverts() public {
        uint256 victimIn = 20_000e18;
        uint256 expected = pool.quote(victimIn, pool.ra(), pool.rb());
        vm.prank(bot); pool.swapAforB(30_000e18, 0);
        vm.prank(victim);
        vm.expectRevert(bytes("slippage: output below minimum"));
        pool.swapAforB(victimIn, expected * 995 / 1000);            // only 0.5% tolerated
        console2.log("victim swap with 0.5% limit reverted, no loss");
    }

    function test_5_oracleManipulation() public {
        Lender lender = new Lender(usd, gold, pool);
        usd.mint(address(lender), 1_000_000e18);                     // the desk has 1,000,000 USD to lend
        vm.startPrank(trader);
        gold.approve(address(lender), type(uint256).max);
        lender.deposit(10e18);                                       // 10 GOLD as collateral
        uint256 honestLimit = 10e18 * pool.spotPriceB() / 1e18 / 2;
        console2.log("borrow limit, fair price:", fmt(honestLimit));
        uint256 g = pool.swapAforB(400_000e18, 0);                  // pump the spot price inside the same transaction
        console2.log("spot price after pump   :", fmt(pool.spotPriceB()));
        uint256 limit = 10e18 * pool.spotPriceB() / 1e18 / 2;
        lender.borrow(limit);
        console2.log("borrowed USD            :", fmt(limit));
        uint256 back = pool.swapBforA(g, 0);                         // dump the GOLD back
        console2.log("swap cost (USD lost)    :", fmt(400_000e18 - back));
        console2.log("spot price after dump   :", fmt(pool.spotPriceB()));
        vm.stopPrank();
    }
}
```

نشغل الاختبارات في Image مجموعة Foundry، وننسخ المشروع إلى مجلد داخل ال Container قبل البناء، والسبب أن الكتابة في مجلد مربوط من Windows بطيئة جداً كما ذكرنا في مقال الاختبار من خمسة أسئلة:

```bash
docker run --rm --name lab-defi-forge -v "$PWD:/src" ghcr.io/foundry-rs/foundry:v1.8.6 \
  "cp -r /src /tmp/defi && cd /tmp/defi && forge install foundry-rs/forge-std --no-git >/dev/null 2>&1; forge test -vv"
```

وسوف نقرأ المخرج سيناريو بعد سيناريو في الأقسام التالية، وكل سيناريو يبدأ من نفس الحالة التي يبنيها `setUp`: صندوق فيه 100,000 USD و100 GOLD، ومتداول وضحية وبوت لكل منهم مليون USD وألف GOLD، والسبب أن Foundry يعيد تشغيل `setUp` قبل كل اختبار، فلا يؤثر سيناريو على الذي بعده، وهذا ما يجعل الأرقام قابلة للمقارنة.

### كيف يختبر المطورون مثل هذا الكود؟ اختبار الخصائص

الأمثلة الخمسة أعلاه تختبر حالات اخترناها نحن، ولكن الثغرات في العقود الحقيقية تأتي غالباً من حالات لم يفكر فيها أحد. لذلك يكتب مطورو العقود اختبارات خصائص Property Tests: بدلاً من أن تقول «إذا اشتريت بـ 1,000 تحصل على 0.9871»، تقول «مهما كان المبلغ ومهما كان الاتجاه، يجب ألا ينقص حاصل ضرب الرصيدين»، وتترك أداة الاختبار تولد مدخلات عشوائية كثيرة وتبحث عن مدخل يخالف الخاصية. وهذه الخاصية مهمة لأن نقص حاصل الضرب يعني أن أحداً أخذ من الصندوق أكثر مما أدخل. احفظ الملف التالي باسم `test/Invariant.t.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {Token} from "../src/Token.sol";
import {Pool} from "../src/Pool.sol";

// Fuzz property: whatever amount anyone swaps, the product of the reserves never goes down (fees only add to it).
contract PoolFuzz is Test {
    Token usd; Token gold; Pool pool;

    function setUp() public {
        usd = new Token("Lab USD", "USD"); gold = new Token("Lab Gold", "GOLD");
        pool = new Pool(usd, gold);
        usd.mint(address(this), 10_000_000e18); gold.mint(address(this), 10_000e18);
        usd.approve(address(pool), type(uint256).max); gold.approve(address(pool), type(uint256).max);
        pool.addLiquidity(100_000e18, 100e18);
    }

    function testFuzz_kNeverDecreases(uint256 amtIn, bool aToB) public {
        amtIn = bound(amtIn, 1e12, 1_000_000e18);
        uint256 kBefore = pool.ra() * pool.rb();
        if (aToB) pool.swapAforB(amtIn, 0); else pool.swapBforA(amtIn / 1000, 0);
        assertGe(pool.ra() * pool.rb(), kBefore, "k decreased");
    }
}
```

```bash
docker run --rm --name lab-defi-forge -v "$PWD:/src" ghcr.io/foundry-rs/foundry:v1.8.6 \
  "cp -r /src /tmp/defi && cd /tmp/defi && forge install foundry-rs/forge-std --no-git >/dev/null 2>&1; forge test --match-contract PoolFuzz"
```

والجزء الأخير من المخرج سوف يكون كما يلي:

```text
Ran 1 test for test/Invariant.t.sol:PoolFuzz
[PASS] testFuzz_kNeverDecreases(uint256,bool) (runs: 256, μ: 103721, ~: 103939)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 26.82ms (24.78ms CPU time)
```

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

```bash
docker run --rm --name lab-defi-forge -v "$PWD:/src" ghcr.io/foundry-rs/foundry:v1.8.6 \
  "cp -r /src /tmp/defi && cd /tmp/defi && forge install foundry-rs/forge-std --no-git >/dev/null 2>&1; sed -i 's/amtIn \* 997/amtIn * 1003/' src/Pool.sol && grep -n 'inWithFee = ' src/Pool.sol && forge test --match-contract PoolFuzz"
```

والأسطر المهمة من المخرج سوف تكون كما يلي:

```text
31:        uint256 inWithFee = amtIn * 1003;
Ran 1 test suite in 20.33ms (13.87ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
[FAIL: k decreased: 9999999999995210730253178710825127876455296 < 10000000000000000000000000000000000000000000; counterexample: calldata=0x78a3ecf400000000000000000000000000000000000000000000000000000e84f66827ab0000000000000000000000000000000000000000000000000000000000000000 args=[15964232492971 [1.596e13], false]] testFuzz_kNeverDecreases(uint256,bool) (runs: 0, μ: 0, ~: 0)
```

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

## أثر السعر Price Impact: لماذا يرتفع السعر عليك؟

السيناريو الأول يسأل الصندوق كم GOLD يعطي مقابل ثلاثة أحجام شراء، دون أن ينفذ أي عملية:

```text
[PASS] test_1_priceImpact() (gas: 146474)
Logs:
  spot price GOLD (USD)   : 1000.0000
  pay 1000.0000 USD -> GOLD 0.9871 | avg price 1013.0090
  pay 10000.0000 USD -> GOLD 9.0661 | avg price 1103.0090
  pay 50000.0000 USD -> GOLD 33.2665 | avg price 1503.0090
```

في المخرج أعلاه لاحظ التالي:

- السعر الآني 1,000، ولكن من يشتري بـ 1,000 USD يدفع في المتوسط 1,013 لكل GOLD، والفرق 1.3%: قرابة 0.3% منه رسوم، والباقي أثر السعر، لأن كل جزء من الشراء يرفع السعر للجزء الذي بعده.
- الشراء بـ 10,000، أي عشر رصيد USD في الصندوق، يرفع المتوسط إلى 1,103، والشراء بـ 50,000، أي نصف رصيد الصندوق، يرفعه إلى 1,503، فيدفع المشتري أكثر بخمسين في المئة من السعر الذي رآه على الشاشة.
- أثر السعر يعتمد على حجم العملية مقارنة بحجم الصندوق، وليس على المبلغ وحده، فنفس الـ 50,000 في صندوق أكبر بمئة مرة يحرك السعر قليلاً جداً.

![ثلاثة أشرطة لأحجام الشراء: 1,000 USD تعطي 0.9871 GOLD بمتوسط 1,013، و10,000 USD تعطي 9.0661 GOLD بمتوسط 1,103، و50,000 USD تعطي 33.2665 GOLD بمتوسط 1,503، وتحتها بطاقتان: لماذا يحدث هذا لأن كل وحدة تشتريها تجعل التالية أغلى، وحد الانزلاق الذي يرفض المعاملة إذا تحرك السعر أكثر من المسموح](https://arabroot.io/content/images/2026/10/defi-basics-02-impact.webp)

نفس الصندوق ونفس السعر الآني، ومتوسط سعر يرتفع مع حجم الطلب

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

### لماذا يكون أحد الرمزين غالباً عملة مستقرة؟

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

## مزودو السيولة: من أين يأتي العائد؟

هذا هو مصدر رقم العائد الذي بدأنا به. مزود السيولة Liquidity Provider يودع الرمزين في الصندوق بنفس نسبة أسعارهما، ويحصل على حصص، وكل عملية تبادل تترك 0.3% رسوماً في الصندوق، فتزيد قيمة كل حصة قليلاً. وعندما يسحب المزود يأخذ نصيبه من الرصيدين كما هما يومها. وهذا هو العائد الذي يظهر على المواقع: رسوم التداول مقسومة على حجم الصندوق، محسوبة على فترة ماضية ومضروبة لتصبح رقماً سنوياً. ولاحظ الكلمة المهمة: ماضية، فالرقم يتغير مع حجم التداول كل يوم، ولا يضمنه أحد.

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

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

### تجربة: الخسارة غير الدائمة Impermanent Loss

السيناريو الثاني يجعل متداولاً يشتري من الصندوق بـ 100,000 USD، فيرتفع سعر GOLD في الصندوق قرابة أربعة أضعاف، ثم يسحب مزود السيولة كل حصته، ونقارن ما سحبه بما كان سيملكه لو احتفظ بالرمزين في محفظته:

```text
[PASS] test_2_impermanentLoss() (gas: 247599)
Logs:
  spot price after trades: 3993.9999
  LP withdraws USD        : 200000.0000
  LP withdraws GOLD       : 50.0751
  value if provided (USD) : 399999.9999
  value if just held (USD): 499399.9999
  difference % x100       : 1990
```

في المخرج أعلاه لاحظ التالي:

- المزود أودع 100,000 USD و100 GOLD، وسحب 200,000 USD و50.0751 GOLD فقط، لأن المتداول اشترى منه قرابة نصف GOLD الذي كان في الصندوق.
- بالسعر الجديد قرابة 3,994، قيمة ما سحبه قرابة 400,000، بينما قيمة ما كان سيملكه لو احتفظ بالرمزين قرابة 499,400، والفرق 19.90%. ونفس الحساب النظري لتغير السعر أربعة أضعاف في صندوق بحاصل ضرب ثابت يعطي قرابة 20%، والفرق الصغير من رسوم العملية التي بقيت في الصندوق.
- المزود لم يخسر بالمقارنة مع ما أودعه بالسعر القديم، فقيمة ما سحبه أكبر، ولكنه خسر بالمقارنة مع الاحتفاظ بالرمزين، وهذا هو الثمن الحقيقي لتوفير السيولة: أنت تبيع الأصل الذي يرتفع للمتداولين في الطريق.

![ثلاث بطاقات: لو احتفظ مزود السيولة بالرمزين لكانت قيمتهما 499,400، وما سحبه من الصندوق 200,000 USD و50.0751 GOLD بقيمة 400,000، والفرق 19.90%، وتحتها شرح لماذا تسمى غير دائمة: إذا رجع السعر رجع الفرق إلى الصفر، وإذا سحب المزود والسعر مختلف أصبحت حقيقية](https://arabroot.io/content/images/2026/10/defi-basics-03-il.webp)

الصندوق يبيع للمتداولين الأصل الذي يرتفع، والفرق مع الاحتفاظ هو ثمن توفير السيولة

وتسمى «غير دائمة» لأن السعر إذا رجع إلى ما كان عليه عند الإيداع رجع الفرق إلى الصفر، أما إذا سحب المزود والسعر مختلف فالخسارة حقيقية تماماً. وتشرح [وثائق Uniswap عن عوائد مزودي السيولة](https://docs.uniswap.org/contracts/v2/concepts/advanced-topics/understanding-returns?ref=arabroot.io) أن العائد الفعلي هو الرسوم المكتسبة ناقص أثر تغير الأسعار، وأن المزود قد يخسر بالمقارنة مع الاحتفاظ رغم الرسوم. والدرس لرقم العائد الذي بدأنا به: الرقم الذي على الموقع هو الجزء الأول من المعادلة فقط، والجزء الثاني يعتمد على حركة السعر التي لا يتوقعها أحد، وفي الأيام التي يتحرك فيها السعر بقوة قد يكون الجزء الثاني أكبر من الأول بكثير.

## ترتيب المعاملات والقيمة القابلة للاستخراج MEV

المعاملة على Ethereum لا تدخل الكتلة لحظة توقيعها، وإنما تنتظر في طابور علني يراه الجميع، كما رأينا مع طابور Bitcoin في مقال كيف يعمل Bitcoin، ومن يبني الكتلة يختار المعاملات وترتيبها. وهذا يفتح باباً للربح لمن يرى المعاملات قبل تنفيذها: إذا رأى معاملة شراء كبيرة سوف ترفع السعر، يستطيع أن يشتري قبلها ويبيع بعدها. وتسمى القيمة التي يمكن استخراجها بإضافة المعاملات أو حذفها أو ترتيبها القيمة القابلة للاستخراج الأقصى Maximal Extractable Value، واختصاراً MEV، كما تشرح [صفحة MEV في ethereum.org](https://ethereum.org/en/developers/docs/mev/?ref=arabroot.io)، وأشهر صورها التي تضر المستخدم مباشرة هجوم الساندويتش Sandwich.

### تجربة: هجوم الساندويتش

في السيناريو الثالث تريد الضحية أن تشتري بـ 20,000 USD، وتقبل انزلاقاً حتى 5%، وهو حد شائع في الواجهات لتجنب فشل المعاملات. والبوت يرى المعاملة فيشتري قبلها بـ 2,000، ثم يبيع بعدها ما اشتراه. ونحن نرتب المعاملات داخل الاختبار بأوامر `vm.prank` كما يرتبها من يبني الكتلة:

```text
[PASS] test_3_sandwich() (gas: 294521)
Logs:
  victim expects GOLD     : 16.6249
  victim receives GOLD    : 16.0326
  victim loss vs expected : 0.5923
  bot spent USD           : 2000.0000
  bot got back USD        : 2832.2085
```

الضحية حصلت على 16.0326 GOLD بدلاً من 16.6249 المتوقعة، والفرق 0.5923 GOLD، أي قرابة 590 USD بسعر البداية، والبوت دفع 2,000 واسترد 2,832.21، أي ربح قرابة 832 USD من عملية واحدة دون أي مخاطرة سعرية تقريباً. ولاحظ أن الضحية لم يحدث لها أي خطأ تراه: المعاملة نجحت، والكمية ضمن الحد الذي قبلته، فقط بسعر أسوأ مما رأت على الشاشة.

وهنا ظهرت لنا مشكلة حقيقية في أول نسخة من الاختبار: جعلنا البوت يشتري بـ 30,000، فتحرك السعر أكثر من 5%، ففشلت معاملة الضحية برسالة `slippage: output below minimum`، وفشل الاختبار كله. ثم جربنا 5,000 فتكرر الفشل، ونجح الهجوم بـ 2,000\. والدرس أن البوت الحقيقي لا يشتري أي مبلغ، وإنما يحسب أكبر مبلغ يبقي الضحية داخل حدها، لأن معاملة الضحية إذا فشلت لا يربح البوت شيئاً. وبالتالي حد الانزلاق ليس رقماً للراحة، وإنما الحد الأقصى لما يستطيع غيرك أن يأخذه منك. وفي السيناريو الرابع جعلنا الضحية تقبل 0.5% فقط، والبوت يشتري بـ 30,000 قبلها:

```text
[PASS] test_4_tightSlippageReverts() (gas: 138066)
Logs:
  victim swap with 0.5% limit reverted, no loss
```

معاملة الضحية رفضت، فلم تشتر بالسعر المرفوع، وخسارتها الوحيدة رسوم الغاز للمعاملة الفاشلة على الشبكة الحقيقية، والبوت بقي يحمل GOLD اشتراه بسعر مرتفع دون أن يبيعه لأحد. والصورة التالية تجمع السيناريوهين:

![ثلاث خطوات: البوت يشتري أولاً بـ 2,000 USD، ثم الضحية تشتري بحد انزلاق 5% فتحصل على 16.0326 GOLD بدلاً من 16.6249 بخسارة 0.5923، ثم البوت يبيع بعدها ويسترد 2,832.21 USD، وتحتها بطاقتان: الحد الضيق 0.5% جعل معاملة الضحية ترفض دون خسارة، والمشكلة التي ظهرت أن البوت بـ 30,000 تجاوز حد الضحية فرفضت معاملتها، فالبوت الحقيقي يحسب أكبر مبلغ داخل الحد](https://arabroot.io/content/images/2026/10/defi-basics-04-sandwich.webp)

حد الانزلاق الواسع هو الحد الأقصى لما يستطيع البوت أن يأخذه منك

ولأي تطبيق ادخار يتعامل مع هذه الصناديق: إذا كان التطبيق سيشتري ويبيع نيابة عن العملاء، فهو يحتاج سياسة انزلاق محسوبة لكل حجم، وطريقة إرسال للمعاملات لا تعرضها في الطابور العام إن أمكن، ومراقبة لسعر التنفيذ الفعلي مقارنة بالسعر المعروض، وإلا تحول عملاؤه إلى مصدر دخل ثابت لبوتات لا يعرفها أحد.

## الإقراض ومصادر الأسعار Oracles

النوع الثاني الأشهر في التمويل اللامركزي هو الإقراض بضمان: تودع أصلاً كضمان، وتقترض أصلاً آخر حتى نسبة من قيمة الضمان، وإذا انخفضت قيمة الضمان تحت حد معين يستطيع أي شخص أن يسدد جزءاً من دينك ويأخذ ضمانك بخصم، وهذا ما يسمى التصفية Liquidation. وكل هذا يحتاج شيئاً واحداً لا يعرفه العقد بنفسه: سعر الأصل. فالعقد على السلسلة لا يرى الأسواق، ويحتاج مصدراً يعطيه السعر، ويسمى مصدر الأسعار Oracle. والحل البسيط الذي يخطر على البال أن يقرأ العقد السعر من صندوق تبادل على نفس الشبكة، فهو على السلسلة وفي متناول اليد، وهذا ما فعله عقد `Lender` في تجربتنا.

وقد حذرت الورقة التقنية لـ Uniswap v2 نفسها من ذلك، فهي تقول إن Uniswap v1 ليس آمناً كمصدر أسعار على السلسلة لأنه سهل التلاعب جداً، وتشرح أن المهاجم يستطيع أن يشتري من الصندوق ليرفع السعر، ثم يستدعي العقد الذي يقرأ السعر، ثم يبيع ليعيده، كل ذلك في معاملة واحدة. ولذلك أضافت v2 مصدر أسعار بمتوسط زمني Time-Weighted Average Price، واختصاراً TWAP.

### التصفية Liquidation: ماذا يحدث عندما ينخفض الضمان؟

عقد الإقراض في تجربتنا بسيط عمداً، فهو لا يفعل شيئاً إذا انخفض سعر الضمان بعد الاقتراض. أما العقود الحقيقية فتحدد حداً أعلى من نسبة الاقتراض، مثلاً إذا أصبح الدين أكثر من 80% من قيمة الضمان أصبح المركز قابلاً للتصفية، فيستطيع أي شخص أن يسدد جزءاً من الدين ويأخذ مقابله ضماناً بقيمة أكبر قليلاً من المبلغ الذي سدده، وهذا الفرق هو مكافأة المصفي التي تجعل الناس يراقبون المراكز ويصفونها بسرعة. وبأرقام تجربتنا: من أودع 10 GOLD بسعر 1,000 واقترض 5,000، يصبح دينه 80% من الضمان إذا انخفض سعر GOLD إلى 625، فإذا وصل السعر إلى هذا الحد خسر المقترض جزءاً من ضمانه للمصفي.

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

### تجربة: نقترض بضمان نرفع سعره بأنفسنا

في السيناريو الخامس يودع المهاجم 10 GOLD ضماناً، وقيمتها بالسعر العادل 10,000، فيستطيع أن يقترض 5,000 فقط. ثم في نفس المعاملة يشتري من الصندوق بـ 400,000 USD، ويقترض، ثم يبيع ما اشتراه:

```text
[PASS] test_5_oracleManipulation() (gas: 510323)
Logs:
  borrow limit, fair price: 5000.0000
  spot price after pump   : 24939.9999
  borrowed USD            : 124699.9999
  swap cost (USD lost)    : 481.5881
  spot price after dump   : 1004.8158
```

في المخرج أعلاه لاحظ التالي:

- الشراء الضخم رفع السعر الآني في الصندوق من 1,000 إلى قرابة 24,940، أي قرابة 25 ضعفاً، لأن المبلغ أكبر بأربع مرات من رصيد USD في الصندوق.
- عقد الإقراض قرأ السعر في تلك اللحظة، فرأى أن 10 GOLD تساوي قرابة 249,400، وسمح باقتراض نصفها، 124,700 USD.
- بيع GOLD بعد ذلك أعاد السعر إلى قرابة 1,005، وكلف المهاجم 481.59 USD فقط، وهي في أغلبها رسوم الصندوق على العمليتين.
- النتيجة أن المهاجم يملك قرضاً بـ 124,700 بضمان يساوي 10,000، ولن يسدده، فالعقد خسر قرابة 114,700 ناقص تكلفة صغيرة، وكل ذلك في معاملة واحدة صحيحة بكل قواعد الشبكة.

![أربع خطوات في معاملة واحدة: ضمان 10 GOLD بحد اقتراض 5,000 بالسعر العادل، ثم شراء ضخم بـ 400,000 USD يرفع السعر الآني إلى 24,940، ثم الاقتراض بالسعر المرفوع 124,700 USD، ثم البيع وإعادة السعر إلى 1,004.8 بتكلفة 481.59 USD، وتحتها الحل: متوسط زمني TWAP أو مصدر أسعار مستقل كما توصي وثائق Uniswap](https://arabroot.io/content/images/2026/10/defi-basics-05-oracle.webp)

عقد يقرأ السعر الآني من صندوق واحد يقرض بسعر يحدده المقترض نفسه

وفي الواقع لا يحتاج المهاجم أن يملك 400,000 أصلاً، فالورقة التقنية لـ Uniswap v2 نفسها تصف «الاقتراض الخاطف» Flash Swaps، وهو أن تأخذ أصولاً من الصندوق وتستخدمها ثم تعيدها في نهاية نفس المعاملة، وكثير من المنصات توفر قروضاً خاطفة Flash Loans بنفس المنطق، فيصبح رأس مال الهجوم رسوماً صغيرة فقط. والحل كما تشرح [وثائق مصادر الأسعار في Uniswap v2](https://developers.uniswap.org/docs/protocols/v2/concepts/oracles?ref=arabroot.io) أن تعتمد على متوسط زمني يحسب من الأسعار في بداية كل كتلة، وتذكر الوثائق أن كلفة التلاعب بهذا المتوسط تزيد تقريباً بشكل خطي مع حجم السيولة ومع طول مدة المتوسط، أو أن تعتمد على مصادر أسعار مستقلة تجمع الأسعار من أسواق كثيرة، ولكل منهما ثمنه في التأخير وفي الثقة بمن يشغل المصدر.

### القروض الخاطفة: رأس مال الهجوم بالمجان تقريباً

القرض الخاطف قرض بلا ضمان يجب أن يسدد في نفس المعاملة، فإذا لم يسدد ألغيت المعاملة كلها كأنها لم تحدث، وهذا ممكن فقط على سلسلة تنفذ المعاملة كوحدة واحدة إما كاملة أو لا شيء. والورقة التقنية لـ Uniswap v2 تصفه تحت اسم Flash Swaps، وتقول إنه يسمح بأخذ أي أصل من الصندوق واستخدامه ثم إعادته أو دفع ما يقابله في نهاية المعاملة، برسوم 0.30%، وتقول إن هذا عملياً يشبه السماح لأي شخص باقتراض أي أصل في الصندوق اقتراضاً خاطفاً. وهذه أداة مفيدة للمراجحة وإعادة تمويل القروض دون رأس مال، ولكنها تعني أيضاً أن أي هجوم يحتاج رأس مال كبيراً لمدة معاملة واحدة، مثل هجوم مصدر الأسعار في تجربتنا، أصبح متاحاً لأي شخص بتكلفة الرسوم فقط. لذلك لا تحسب أمان أي تصميم على افتراض أن المهاجم لا يملك المال الكافي.

## مخاطر الكود والحوكمة

كل ما سبق يفترض أن الكود يعمل كما كتب. ولكن الكود نفسه قد يكون فيه خطأ، والعقد المنشور لا يمكن تصحيحه في الأصل كما رأينا في كل المقالات السابقة، والأخطاء فيه تبقى منشورة أمام الجميع، ويستطيع أي شخص أن يستغلها. ولهذا السبب جعلت كثير من المنصات عقودها قابلة للترقية Upgradeable: العقد الذي يتعامل معه المستخدمون مجرد وكيل يشير إلى عقد آخر فيه المنطق، ويستطيع مالك معين أن يغير هذا العقد الآخر. وهذا يحل مشكلة تصحيح الأخطاء، ولكنه يعيد الثقة إلى من يملك صلاحية الترقية: فريق، أو محفظة متعددة التوقيع، أو تصويت حاملي رمز الحوكمة. والسؤال الذي يجب أن يسأله فريقك عن أي منصة: من يستطيع أن يغير الكود الذي يحفظ أموالنا، وكم يحتاج من الوقت، وهل يستطيع أن يجمد السحب؟

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

### الحوكمة برموز التصويت

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

### كيف تقرأ العقد قبل أن تثق به؟

السؤال عن صلاحيات المالك يمكن أن تجيب عنه بنفسك بأدوات بسيطة دون أن تثق بما تقوله صفحة المنصة. فالعقد القابل للترقية يحفظ عادة عنوان العقد الذي فيه المنطق في خانة تخزين معيارية يحددها [EIP-1967](https://eips.ethereum.org/EIPS/eip-1967?ref=arabroot.io)، والخانة `0x360894a1…382bbc` هي خانة عنوان التنفيذ، ويذكر المعيار أن مراقبة هذه الخانات ضرورية لأمن تطبيقات كثيرة، وأن أي دالة تغيرها يجب أن تصدر حدثاً. ويستطيع فريقك أن يقرأ هذه الخانة بالأمر `cast storage` الذي رأيناه في مقالات سابقة، وأن يقرأ من يملك صلاحية الترقية، وأن يتابع الأحداث التي تعلن تغييرها. والمعنى العملي: إذا تغير عنوان التنفيذ في عقد يحفظ أموالك، فالكود الذي راجعته أمس لم يعد هو الكود الذي يعمل اليوم.

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

## النظام في المملكة

قبل أي سؤال تقني، يجب أن يجيب المستشار القانوني عن سؤال النظام. وفي المملكة نشرت هيئة السوق المالية في 12 أغسطس 2018 [بيان اللجنة الدائمة للتوعية والتحذير](https://cma.gov.sa/MediaCenter/PR/Pages/Virtual%5FCurrencies.aspx?ref=arabroot.io) بأن العملات الرقمية الافتراضية غير معتمدة داخل المملكة، وحذرت من التعامل فيها والاستثمار فيها، وذكرت مخاطر منها الخسائر الكبيرة والاحتيال وتقلب الأسعار. ومنتج ادخار يضع أرصدة العملاء في منصات تمويل لامركزي نشاط مالي يمس أموال الجمهور، ويحتاج في أي بلد ترخيصاً ورقابة، وهذا قرار لا يتخذه الفريق التقني ولا فريق المنتج. والذي يستطيع الفريق التقني أن يقدمه هو بالضبط ما فعلناه هنا: فهم دقيق للآلية والمخاطر يعرض على الإدارة والمستشار القانوني، دون توصية بالمشاركة.

## خريطة المخاطر

الصورة التالية تجمع كل طبقات المخاطر التي رأيناها، ومثالاً على كل طبقة من هذا المقال أو المقالات السابقة، وما الذي يقلل كل خطر:

![جدول من سبع طبقات: كود العقد وخطره ثغرة لا تعدل بعد النشر ومثاله The DAO، ومصدر الأسعار وخطره سعر يحرك في نفس المعاملة ومثاله تجربة عقد الإقراض، والسيولة وخطرها أثر السعر والخسارة غير الدائمة، وترتيب المعاملات وخطره الساندويتش، والمفاتيح والموافقات وخطرها موافقة غير محدودة لعقد مخترق، والحوكمة وخطرها من يملك ترقية العقد أو إيقافه، والنظام وخطره نشاط مالي دون ترخيص ومثاله تحذير المملكة 2018، مع ما يقلل كل خطر](https://arabroot.io/content/images/2026/10/defi-basics-06-risks.webp)

كل طبقة لها خطرها، والكود الصحيح في طبقة لا يحمي من خطر في طبقة أخرى

## أخطاء شائعة

- **قراءة العائد السنوي كوعد**: الرقم محسوب من رسوم فترة ماضية، ولا يشمل الخسارة غير الدائمة ولا مخاطر الكود ولا تقلب الأسعار.
- **حد انزلاق واسع «حتى لا تفشل المعاملة»**: كما رأينا، الحد الواسع هو المبلغ الذي تسمح للبوت أن يأخذه.
- **عقد يقرأ السعر الآني**: من أشهر أخطاء المطورين الذين يبنون فوق صناديق التبادل، والسعر الآني يمكن تحريكه في نفس المعاملة.
- **موافقات غير محدودة**: الموافقة على مبلغ لا نهائي توفر الرسوم اليوم، وتترك رصيدك كله مكشوفاً إذا اخترق العقد غداً.
- **تجاهل صلاحيات المالك**: «لامركزي» في الاسم لا يعني أن أحداً لا يستطيع تغيير العقد، فاقرأ من يملك الترقية والإيقاف.
- **نسخ أرقام الاختبار إلى الإنتاج**: أرقامنا من صندوق صغير بشروط اخترناها، والصندوق الحقيقي يختلف حجمه ورسومه ومعادلته، فأعد الحساب على العقد الحقيقي بنسخة محلية من الشبكة قبل أي قرار.
- **البدء من التقنية قبل النظام**: أي منتج يمس أموال العملاء يبدأ بالرأي القانوني، لا بالتكامل.

## قائمة أسئلة لأي مقترح منتج من هذا النوع

| السؤال                                                         | لماذا نسأله                                                 |
| -------------------------------------------------------------- | ----------------------------------------------------------- |
| ما مصدر العائد بالضبط، رسوم أم حوافز رموز أم إقراض؟            | كل مصدر له خطر مختلف، والحوافز قد تتوقف في أي وقت           |
| كم كانت الخسارة غير الدائمة في أسوأ شهر ماض لهذا الصندوق؟      | العائد المعروض لا يشملها                                    |
| من أين يقرأ العقد الأسعار، ومتوسط أي مدة؟                      | السعر الآني يمكن تحريكه كما رأينا                           |
| هل العقد قابل للترقية، ومن يملك ذلك؟                           | الثقة تنتقل إلى صاحب الصلاحية                               |
| هل روجع الكود أمنياً، ومتى، ومن أي جهة، وهل تغير بعد المراجعة؟ | المراجعة القديمة لا تغطي كوداً جديداً                       |
| ما الذي يحدث لأموال العملاء إذا توقف العقد أو جمد؟             | لا يوجد ضمان ودائع ولا جهة تعويض                            |
| ما رأي المستشار القانوني في الأنظمة السارية؟                   | في المملكة صدر تحذير رسمي بأن العملات الافتراضية غير معتمدة |

## إزالة التجربة

```bash
docker image rm ghcr.io/foundry-rs/foundry:v1.8.6
```

## تدريب عملي

1. غير الصندوق في الاختبار ليبدأ بعشرة أضعاف السيولة، وأعد السيناريو الأول، وقارن أثر السعر لنفس الأحجام.
2. أضف إلى `Pool.sol` متغيراً يسجل السعر في أول عملية من كل كتلة، واجعل `Lender` يقرأه بدلاً من السعر الآني، ثم أعد السيناريو الخامس باستخدام `vm.roll` لتغيير الكتلة.
3. في السيناريو الثالث احسب أكبر مبلغ يستطيع البوت أن يشتري به دون أن تفشل معاملة الضحية بحد 5%، وقارن ربحه.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- التمويل اللامركزي تطبيقات مالية كعقود ذكية علنية، تعطي وصولاً مفتوحاً وتسوية فورية، ومعها غياب أي جهة تعوض الخسارة.
- صانع السوق الآلي يحدد السعر بنسبة رصيدين وفق x · y = k، والشراء الكبير يرفع متوسط السعر كما رأينا من 1,013 إلى 1,503.
- عائد مزودي السيولة رسوم التداول ناقص أثر تغير الأسعار، ورأينا خسارة غير دائمة قرابة 20% عندما تغير السعر أربعة أضعاف.
- من يبني الكتلة يرتب المعاملات، والبوت أخذ قرابة 832 USD من ضحية قبلت انزلاقاً 5%، والحد الضيق حماها.
- عقد يقرأ السعر الآني من صندوق واحد أقرض 124,700 بضمان قيمته 10,000، والحل متوسط زمني أو مصادر مستقلة.
- مخاطر الكود والحوكمة والنظام تبقى حتى لو كانت الرياضيات صحيحة، وأي منتج يمس أموال العملاء يبدأ بالرأي القانوني.

[![](https://arabroot.io/content/images/2026/10/blockchain-basics-cover-cover.webp)دليلكيف يعمل البلوكتشين Blockchain ومتى لا تحتاجهما المشكلة التي يحلها البلوكتشين أصلاً وكيف يعمل من الداخل، ومتى تكون قاعدة البيانات العادية هي الحل الأصح.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D9%85%D8%AA%D9%89-%D9%84%D8%A7-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC%D9%87/)[![](https://arabroot.io/content/images/2026/10/crypto-wallets-keys-cover-cover.webp)دليلالمحافظ والمفاتيح: العبارة السرية وكيف تحمي أصولكما الذي تحفظه المحفظة فعلاً، وكيف تولد اثنتا عشرة كلمة كل مفاتيحك، وكيف تفصل شركة صغيرة بين معرفة الأرصدة والتوقيع.](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%AD%D8%A7%D9%81%D8%B8-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D8%A7%D9%84%D8%B9%D8%A8%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%88%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D9%85%D9%8A-%D8%A7%D8%B5%D9%88%D9%84%D9%83/)[![](https://arabroot.io/content/images/2026/10/core-banking-cover-cover.webp)دليلالنظام المصرفي الأساسي Core Banking: البنك من الداخلما الذي يحفظه النظام المصرفي الأساسي وكيف تمر فيه الحركة، ولماذا يعمل بتاريخ عمل ودورة ليلية، مع مختبر لاستحقاق العائد على ألف حساب.](https://arabroot.io/articles/%D8%A7%D9%84%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%B5%D8%B1%D9%81%D9%8A-%D8%A7%D9%84%D8%A7%D8%B3%D8%A7%D8%B3%D9%8A-core-banking-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D8%A8%D9%86%D9%83-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84/)[![](https://arabroot.io/content/images/2026/10/card-payment-flow-cover-cover.webp)دليلكيف يعمل الدفع بالبطاقة؟ التفويض Authorization والتسويةمن ضغطة «ادفع» إلى آخر اعتراض: التفويض و3D Secure والتحصيل والتسوية والاسترداد والاعتراض، مع مختبر لأحداث مزود الدفع ومطابقتها.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%AF%D9%81%D8%B9-%D8%A8%D8%A7%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%81%D9%88%D9%8A%D8%B6-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A7%D8%B9%D8%AA%D8%B1%D8%A7%D8%B6/)[![](https://arabroot.io/content/images/2026/10/layer2-scaling-cover-cover.webp)دليلطبقات التوسع Layer 2: كيف تقلل الرسوم وتسرع المعاملاتنبني طبقة ثانية متفائلة مصغرة ونقيس تكلفة مئة تحويل على السلسلة الأم مقابل دفعة وBlob، ثم نكشف مشغلاً كاذباً بإثبات الاحتيال.](https://arabroot.io/articles/%D8%B7%D8%A8%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%88%D8%B3%D8%B9-layer-2-%D9%83%D9%8A%D9%81-%D8%AA%D9%82%D9%84%D9%84-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85-%D9%88%D8%AA%D8%B3%D8%B1%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%85%D9%84%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/need-blockchain-test-cover-cover.webp)دليلهل تحتاج بلوكتشين أم قاعدة بيانات؟ اختبار من خمسة أسئلةخمسة أسئلة تحدد هل يحتاج مشروعك بلوكتشين، مع تجربة تبني البديل الأرخص في PostgreSQL وتقيس تكلفة العقد الذكي.](https://arabroot.io/articles/%D9%87%D9%84-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC-%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D8%A7%D9%85-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D9%85%D9%86-%D8%AE%D9%85%D8%B3%D8%A9-%D8%A7%D8%B3%D8%A6%D9%84%D8%A9/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، واختبار التجارب على Docker Engine 29.8.0 مع Foundry v1.8.6 وSolc 0.8.30 وforge-std v1.17.0.