مقدمة في التمويل اللامركزي (DeFi): كيف تعمل منصات التبادل مثل Uniswap برمجياً؟

دقائق القراءة: 6

مقدمة في التمويل اللامركزي DeFi ولماذا تختلف منصات التبادل اللامركزية؟

التمويل اللامركزي DeFi هو طبقة تطبيقات مالية تعمل فوق شبكات البلوكتشين دون وسيط تقليدي مثل البنك أو شركة المقاصة. بدلاً من الاحتفاظ بسجل حسابات مركزي، تعتمد هذه الأنظمة على Smart Contracts تنفذ القواعد البرمجية بشكل حتمي وشفاف.

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

منصات مثل Uniswap لا تستخدم دفتر أوامر تقليدياً Order Book كما في البورصات المركزية، بل تعتمد نموذج AMM أو صانع السوق الآلي. هنا يصبح السعر ناتجاً عن معادلة رياضية ومخزون السيولة داخل العقد، لا عن مطابقة بين مشترٍ وبائع.

كيف تعمل منصة تبادل لامركزية برمجياً؟

في أبسط شكل، تحتاج منصة التبادل إلى ثلاثة مكونات مترابطة:

على مستوى EVM، كل عملية تبادل هي معاملة تستدعي دالة داخل العقد الذكي. المستخدم يوافق أولاً على منح العقد حق سحب الرمز باستخدام approve، ثم يستدعي دالة التبديل، والعقد يعيد حساب السعر ويرسل الأصل المقابل.

هذا النوع من التكامل يعتمد كثيراً على الواجهات (Interfaces): التحدث مع عقود ذكية لا تملك كودها المصدري (مثل منصات التبادل) لأن عقد التبادل يحتاج لمخاطبة عقود الرموز بدون امتلاك كودها الكامل داخله.

فكرة مجمعات السيولة Liquidity Pools

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

النسخة الكلاسيكية من Uniswap V2 تعتمد معادلة المنتج الثابت:

x * y = k

حيث x وy هما رصيدا الأصلين داخل المجمع، وk قيمة يجب أن تبقى ثابتة تقريباً بعد التبديل مع احتساب الرسوم.

هذه المعادلة هي قلب المنصة البرمجي، لأنها تحدد السعر الفوري Spot Price والانزلاق السعري Slippage كلما كبر حجم الصفقة مقارنة بحجم السيولة.

مثال برمجي مبسط لعقد مجمع سيولة

الكود التالي ليس نسخة إنتاجية من Uniswap، لكنه يوضح البنية الذهنية الأساسية: احتياطيات، إيداع سيولة، وحساب ناتج التبديل.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract SimpleAMM {
    address public token0;
    address public token1;

    uint256 public reserve0;
    uint256 public reserve1;

    event LiquidityAdded(address indexed provider, uint256 amount0, uint256 amount1);
    event Swapped(address indexed user, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut);

    constructor(address _token0, address _token1) {
        token0 = _token0;
        token1 = _token1;
    }

    function addLiquidity(uint256 amount0, uint256 amount1) external {
        require(amount0 > 0 && amount1 > 0, "Invalid amounts");

        IERC20(token0).transferFrom(msg.sender, address(this), amount0);
        IERC20(token1).transferFrom(msg.sender, address(this), amount1);

        reserve0 += amount0;
        reserve1 += amount1;

        emit LiquidityAdded(msg.sender, amount0, amount1);
    }

    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256) {
        require(amountIn > 0, "Invalid input");
        require(reserveIn > 0 && reserveOut > 0, "No liquidity");

        uint256 amountInWithFee = amountIn * 997;
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 1000) + amountInWithFee;

        return numerator / denominator;
    }

    function swapToken0ForToken1(uint256 amountIn) external {
        uint256 amountOut = getAmountOut(amountIn, reserve0, reserve1);
        require(amountOut < reserve1, "Insufficient liquidity");

        IERC20(token0).transferFrom(msg.sender, address(this), amountIn);
        IERC20(token1).transfer(msg.sender, amountOut);

        reserve0 += amountIn;
        reserve1 -= amountOut;

        emit Swapped(msg.sender, token0, amountIn, token1, amountOut);
    }
}

كيف يحسب العقد السعر وكمية الإخراج؟

الدالة getAmountOut هي الجزء الرياضي الأهم. هي تأخذ قيمة الإدخال والاحتياطيين، ثم تخصم الرسوم داخلياً عبر الضرب في 997/1000، وهو تمثيل شائع لرسوم 0.3%.

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

في العقود الحقيقية لا يكفي تحديث reserve0 وreserve1 يدوياً فقط، بل يجب التحقق من الأرصدة الفعلية، وحماية مسار التنفيذ من الثغرات، واختبار الحالات الحدية مثل الرسوم غير المتوقعة أو الرموز التي تخصم جزءاً أثناء التحويل.

ما دور مزودي السيولة والرموز التمثيلية؟

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

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

هذه الفكرة تعتمد على التحكم الدقيق في State Variables وطرق التحقق داخل الدوال، وهو ما يرتبط مباشرة بمفاهيم أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables) والدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟.

الواجهة الأمامية والتفاعل مع العقد

لكي يعمل التبادل أمام المستخدم، تحتاج واجهة ويب تستعمل Ethers.js أو مكتبة مشابهة. الواجهة تقرأ الاحتياطيات، تعرض السعر التقريبي، ثم تطلب من المستخدم تنفيذ خطوتين:

  1. إرسال معاملة approve إلى عقد الرمز.
  2. إرسال معاملة swap إلى عقد المجمع.

هذا التدفق العملي يرتبط بمقالات هندسة الويب اللامركزي (Web3.js & Ethers.js): كيف نربط الواجهات بالعقود الذكية؟ وكتابة البيانات وإرسال المعاملات (Transactions) من واجهة الويب إلى العقد الذكي، حيث يتم تحويل أزرار الواجهة إلى استدعاءات حقيقية على الشبكة.

الأمان، الاختبارات، وتحسين استهلاك الغاز

منصات DeFi تتعامل مباشرة مع أموال حقيقية، لذلك الخطأ البرمجي فيها ليس مجرد bug مزعج، بل مخاطرة مالية فورية. يجب فحص الاستدعاءات الخارجية، ترتيب تحديث الحالة قبل التحويلات، واحترام نمط Checks-Effects-Interactions.

إذا كان العقد يستقبل أو يرسل أصولاً أثناء التبديل أو السحب، فيجب مراجعة مخاطر أمن العقود الذكية (1): ثغرة إعادة الدخول (Reentrancy Attack) الشهيرة وكيفية استغلالها، واستخدام وسائل حماية مناسبة مثل أمن العقود الذكية (2): الحماية من ثغرة Reentrancy باستخدام ReentrancyGuard عند الحاجة.

من جهة الأداء، يهم المطور تقليل عمليات القراءة والكتابة المكلفة على التخزين Storage. تخزين الاحتياطيات بطريقة مضغوطة، واستخدام الدوال view وpure في مواضعها، يخفف التكلفة ويجعل الواجهة تقرأ البيانات مجاناً خارج السلسلة.

لتحسين Gas Optimization، احسب القيم الوسيطة في الذاكرة، قلل التكرار في الوصول إلى المتغيرات المخزنة، واختبر أثر كل تعديل باستخدام بيئة الانتقال إلى بيئة العمل الاحترافية: تثبيت إطار عمل Hardhat باستخدام Node.js مع اختبارات موثقة.

كيف تبدأ ببناء نموذجك التجريبي؟

إذا أردت تحويل هذا الفهم إلى مشروع عملي، فالخطوات الصحيحة تكون عادة بهذا الترتيب:

  1. إنشاء رمز أو رمزين تجريبيين متوافقين مع ERC-20.
  2. كتابة عقد AMM مبسط.
  3. اختبار السيناريوهات باستخدام اختبار العقود الذكية محلياً: كتابة اختبارات الوحدة (Unit Tests) باستخدام Chai & Mocha.
  4. نشره عبر أتمتة نشر العقود (Deployment): كتابة سكربت لرفع العقد إلى شبكة Ethereum و Polygon.
  5. ربطه بواجهة تجريبية عبر React وEthers.js.

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

5 comments

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *