القواميس (Mappings): أسرع طريقة لربط عناوين المحافظ بأرصدتها (Key-Value)

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

القواميس Mappings: أسرع طريقة لربط عناوين المحافظ بأرصدتها Key-Value

تُعد Mappings من أهم هياكل التخزين داخل عقود Solidity، لأنها مصممة للوصول السريع إلى البيانات عبر نموذج Key-Value. في تطبيقات Web3، نحتاج باستمرار إلى ربط عنوان محفظة بقيمة محددة مثل الرصيد، صلاحيات الوصول، أو عدد الرموز المملوكة.

إذا كنت قد قرأت أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables) فستلاحظ أن mapping ليس مجرد متغير عادي، بل بنية تخزين عملية جداً داخل EVM. وهي مثالية عندما تريد الوصول إلى قيمة مرتبطة بمفتاح معروف مسبقاً، دون الحاجة للمرور على عناصر متعددة كما يحدث غالباً مع المصفوفات (Arrays) في Solidity: تخزين وإدارة قوائم البيانات داخل العقد الذكي.

ما هي Mappings داخل العقود الذكية؟

Mapping هي بنية تربط بين مفتاح وقيمة. في أبسط مثال، يمكن استخدام النوع address => uint256 لتخزين رصيد كل محفظة. المفتاح هنا هو عنوان المستخدم، والقيمة هي رصيده داخل العقد.

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

الصيغة العامة للتعريف

mapping(keyType => valueType) visibility variableName;

مثال شائع جداً:

mapping(address => uint256) public balances;

هذا السطر ينشئ قاموساً اسمه balances يربط كل address بقيمة من نوع uint256. وعند جعله public، تقوم Solidity تلقائياً بإنشاء دالة قراءة له.

لماذا تعتبر Mappings أسرع من Arrays في هذا السيناريو؟

عندما تريد معرفة رصيد محفظة معينة، فإن استخدام mapping يسمح بالوصول المباشر إلى القيمة عبر المفتاح. أما لو خزّنت البيانات داخل array من السجلات، فقد تحتاج إلى البحث حتى تجد العنوان المطلوب، وهو تصميم أقل كفاءة.

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

استخدم Mappings عندما تكون الأولوية للوصول المباشر بواسطة مفتاح معروف، مثل address. هذا الاختيار يقلل التعقيد ويمنع نماذج بحث مكلفة قد ترفع استهلاك الغاز في العقود الكبيرة.

مثال عملي: ربط عنوان المحفظة برصيدها

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

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

contract WalletBalances {
    mapping(address => uint256) public balances;

    event Deposited(address indexed user, uint256 amount);
    event Transferred(address indexed from, address indexed to, uint256 amount);

    function deposit() external payable {
        require(msg.value > 0, "Amount must be greater than zero");
        balances[msg.sender] += msg.value;

        emit Deposited(msg.sender, msg.value);
    }

    function transferBalance(address to, uint256 amount) external {
        require(to != address(0), "Invalid recipient");
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        balances[to] += amount;

        emit Transferred(msg.sender, to, amount);
    }

    function getMyBalance() external view returns (uint256) {
        return balances[msg.sender];
    }
}

شرح مكونات العقد

  • المتغير balances هو القلب المنطقي للعقد، حيث يخزن رصيد كل مستخدم.
  • الدالة deposit تستقبل Ether وتضيفه إلى رصيد المرسل.
  • الدالة transferBalance تنقل الرصيد المسجل داخلياً من مستخدم إلى آخر.
  • الدالة getMyBalance معرفة كـ view، لذلك لا تعدّل حالة العقد، وهو ما يرتبط بما شرحناه في أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas.

سلوك افتراضي مهم: القيم غير الموجودة

من أكثر النقاط التي تربك المبتدئين أن mapping لا يعيد خطأ إذا لم يكن المفتاح موجوداً سابقاً. بدلاً من ذلك، يعيد القيمة الافتراضية لنوع البيانات. في حالة uint256 ستكون القيمة 0.

هذا يعني أن أي عنوان لم يتفاعل مع العقد بعد سيبدو وكأن رصيده صفر. هذا السلوك مفيد جداً في إدارة الأرصدة، لكنه يتطلب وعياً تصميمياً إذا كنت تريد التمييز بين “غير موجود” و“موجود لكن قيمته صفر”. في هذه الحالة، قد تحتاج إلى mapping إضافي من نوع address => bool.

القيود العملية لـ Mappings

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

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

متى تستخدم Mapping ومتى لا؟

  1. استخدمها عند تخزين رصيد كل مستخدم حسب عنوانه.
  2. استخدمها في أنظمة allowance والصلاحيات.
  3. تجنب الاعتماد عليها وحدها إذا كنت تحتاج استعراض جميع المستخدمين داخل العقد.
  4. ادمجها مع هياكل أخرى عندما يكون المشروع بحاجة إلى سرعة وصول مع قابلية تعداد.

الأمان وتحسين استهلاك الغاز

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

قبل تعديل أي رصيد داخل mapping، تحقق دائماً من الشروط عبر require، وحدث الحالة قبل أي تفاعل خارجي إن وجد. هذه قاعدة أساسية في Security Auditing.

لتقليل استهلاك Gas، لا تُكرر عمليات الكتابة إلى نفس storage slot دون حاجة. وحين يكون الهدف قراءة فقط، استخدم دوال view كما وضحنا سابقاً في الدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟.

كيف تختبر هذا النمط عملياً؟

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

اختبر السيناريوهات التالية خطوة بخطوة:

  • استدعاء deposit مع قيمة موجبة.
  • قراءة الرصيد عبر balances أو getMyBalance.
  • تنفيذ تحويل داخلي إلى عنوان آخر.
  • تجربة تحويل بقيمة أكبر من الرصيد للتأكد من نجاح شرط require.

الخلاصة

تُعتبر Mappings الخيار الأكثر كفاءة عندما تريد ربط عناوين المحافظ بقيم مرتبطة بها مثل الأرصدة أو الصلاحيات. هيكل Key-Value يجعلها مناسبة جداً لعالم Blockchain حيث الكفاءة، الوضوح، وتقليل تكلفة التنفيذ عوامل حاسمة.

لكن القوة الحقيقية لا تأتي من استخدامها وحدها، بل من فهم حدودها ودمجها مع هياكل أخرى عند الحاجة. وكلما تعمقت في تصميم العقود الذكية، ستكتشف أن mapping ليست مجرد أداة تخزين، بل حجر أساس في بناء منطق مالي موثوق داخل تطبيقات Web3. ولتوسيع الصورة من الجذور، يُنصح بالرجوع أيضاً إلى مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟ والتشفير والمفاتيح: كيف تعمل المحافظ الرقمية (Public & Private Keys) برمجياً؟.

23 comments

اترك تعليقاً

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