الواجهات (Interfaces): التحدث مع عقود ذكية لا تملك كودها المصدري (مثل منصات التبادل)

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

الواجهات (Interfaces): التحدث مع عقود ذكية لا تملك كودها المصدري (مثل منصات التبادل)

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

إذا كنت قد قرأت مقال التفاعل بين العقود الذكية: كيف تجعل عقداً يستدعي دالة من عقد آخر؟ فستعرف الفكرة العامة لاستدعاء العقود. لكن interface يذهب خطوة أبعد: هو تعريف مختصر للدوال التي نحتاجها فقط، دون تضمين منطق التنفيذ الداخلي للعقد الهدف.

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

ما هي الواجهة في Solidity؟

الواجهة في Solidity هي تعريف يصف كيف يبدو العقد من الخارج. هي لا تحتوي على منطق تنفيذي، ولا تخزن حالة داخلية، بل تحدد الدوال الخارجية التي يمكن استدعاؤها. فكر فيها كعقد اتفاق بين عقدك والعقد الآخر حول شكل الاتصال.

وهذا مهم لأن EVM لا تحتاج إلى الكود المصدري كي تستدعي عقداً آخر؛ هي تحتاج إلى عنوان العقد وبيانات الاستدعاء المشفرة وفق ABI. الواجهة تمنحك طريقة آمنة ومنظمة لتوليد هذا النداء داخل العقد.

خصائص الواجهة باختصار

  • تعرّف الدوال فقط دون كتابة جسم الدالة.
  • الدوال تكون عادة external.
  • يمكنها إرجاع قيم مثل أي دالة عادية.
  • لا تحتوي على state variables قابلة للتخزين.
  • تستخدم بكثرة مع العقود المنشورة مسبقاً على الشبكة.

متى نستخدم Interface بدلاً من استيراد العقد كاملاً؟

في كثير من المشاريع يكفيك تعريف جزء صغير من العقد الخارجي. مثلاً لو أردت معرفة رصيد مستخدم في عملة معينة، فأنت تحتاج فقط إلى دالة balanceOf من معيار ERC20. ولو أردت تنفيذ مبادلة على منصة تبادل، فقد تحتاج دالة واحدة أو اثنتين فقط من عقد Router.

استخدام الواجهة بدلاً من استيراد العقد الكامل يمنحك عدة فوائد:

  1. تقليل تعقيد الكود داخل المشروع.
  2. وضوح الدوال التي يعتمد عليها عقدك فعلاً.
  3. سهولة التدقيق الأمني لأن سطح التفاعل أصغر.
  4. تقليل احتمالات الخطأ الناتج عن استيراد ملفات غير لازمة.

احرص دائماً على مطابقة توقيع الدالة داخل interface مع العقد الأصلي حرفياً. أي اختلاف في ترتيب المعاملات، نوعها، أو returns سيؤدي غالباً إلى فشل الاستدعاء أو نتائج غير متوقعة.

مثال عملي: واجهة مبسطة لعملة ERC20

قبل التعامل مع منصات التبادل، من المفيد فهم المثال الأشهر: التفاعل مع عقد توكن. هنا نحن لا نعيد كتابة معيار التوكن، بل نصف فقط الدوال التي نحتاجها. ويمكنك مراجعة الدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟ وأنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas لفهم الفرق بين الدوال القرائية والتنفيذية.

pragma solidity ^0.8.20;

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

contract TokenReader {
    function getTokenBalance(address token, address user) external view returns (uint256) {
        return IERC20(token).balanceOf(user);
    }

    function sendToken(address token, address to, uint256 amount) external returns (bool) {
        return IERC20(token).transfer(to, amount);
    }
}

في هذا المثال، قمنا بإنشاء واجهة IERC20 ثم استخدمنا عنوان توكن منشور مسبقاً. السطر IERC20(token).balanceOf(user) يعني: اعتبر هذا العنوان كأنه عقد يلتزم بهذه الواجهة، ثم استدعِ الدالة.

مثال متقدم: التحدث مع عقد تبادل لامركزي

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

pragma solidity ^0.8.20;

interface IRouter {
    function getAmountsOut(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256[] memory amounts);
}

contract PriceChecker {
    function estimateOutput(
        address router,
        uint256 amountIn,
        address tokenIn,
        address tokenOut
    ) external view returns (uint256) {
        address[] memory path = new address[](2);
        path[0] = tokenIn;
        path[1] = tokenOut;

        uint256[] memory amounts = IRouter(router).getAmountsOut(amountIn, path);
        return amounts[1];
    }
}

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

كيف تحصل على الواجهة الصحيحة؟

هناك عدة طرق عملية للحصول على تعريف واجهة موثوق:

  • قراءة التوثيق الرسمي للبروتوكول.
  • الاعتماد على ملفات ABI المنشورة من الفريق.
  • نسخ الواجهة من مكتبات معروفة مثل OpenZeppelin عند التعامل مع المعايير الشهيرة.
  • مراجعة الكود الموثق على مستكشفات مثل Etherscan إن كان العقد موثقاً.

عند العمل عبر Hardhat أو Remix، من الأفضل اختبار الاستدعاءات على شبكة تجريبية أولاً. ويمكن ربط ذلك بما شرحناه سابقاً في إعداد بيئة التطوير: تثبيت محفظة MetaMask والاتصال بشبكات الاختبار (Testnets) والحصول على عملات تجريبية مجانية (Faucet) للبدء في نشر واختبار العقود الذكية.

أخطاء شائعة عند استخدام Interfaces

1) استخدام عنوان خاطئ

قد تكون الواجهة سليمة تماماً، لكنك ترسل إليها عنوان عقد لا يطابقها. مثلاً استخدام عنوان توكن بدل عنوان Router. النتيجة ستكون فشل الاستدعاء أو revert.

2) نسيان صلاحيات التوكن

بعض دوال التبادل تتطلب تنفيذ approve أولاً. تجاهل هذه الخطوة يجعل العقد الخارجي غير قادر على سحب التوكن من عقدك. وهذا مرتبط مباشرة بفهمك لدوال التحويل والتحكم في الأخطاء كما في مقال التعامل مع الأخطاء وإرجاع الأموال: استخدام require, assert, revert.

3) عدم مطابقة نوع الذاكرة

بعض الدوال تتوقع calldata أو تعيد مصفوفات داخل الذاكرة. أي خطأ في النقل من التوثيق الأصلي قد يسبب فشل الترجمة أو سلوكاً غير صحيح. لذلك فهم المصفوفات (Arrays) في Solidity: تخزين وإدارة قوائم البيانات داخل العقد الذكي يصبح مهماً جداً هنا.

من منظور الأمان، لا تفترض أن أي عقد خارجي “آمن” فقط لأنه مشهور. التفاعل مع عقود خارجية يوسّع سطح المخاطر، لذلك طبّق فحوصات قبل وبعد الاستدعاء، ويفضل اعتماد نمط checks-effects-interactions كلما كان ذلك ممكناً.

هل Interfaces تؤثر على استهلاك الغاز؟

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

  • ادمج القراءات عندما يكون ذلك ممكناً.
  • تجنب الاستدعاءات المكررة للعقد نفسه داخل نفس المعاملة.
  • استخدم الدوال view خارج السلسلة إذا كان الهدف مجرد قراءة بيانات للواجهة الأمامية.

لتحسين استهلاك الغاز، لا تجعل العقد ينفذ قراءات يمكن تنفيذها عبر الواجهة الأمامية باستخدام Ethers.js أو viem. ضع الاستدعاء على السلسلة فقط عندما يحتاج منطق العقد نفسه إلى هذه البيانات أثناء تنفيذ معاملة فعلية.

الخلاصة

الـ interfaces ليست مجرد صيغة نحوية داخل Solidity، بل هي أداة هندسية أساسية لبناء تطبيقات مترابطة داخل عالم Blockchain. من خلالها يستطيع عقدك التحدث مع بروتوكولات لا تملك كودها داخل مشروعك، مثل التوكنات الجاهزة ومنصات التبادل ومزودي السيولة.

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

9 comments

اترك تعليقاً

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