المعدلات (Modifiers): حماية الدوال برمجياً (مثل: السماح للمدير فقط بتنفيذ الأمر)
المعدلات (Modifiers): حماية الدوال برمجياً (مثل: السماح للمدير فقط بتنفيذ الأمر)
عند بناء عقد ذكي على Blockchain لا تكفي كتابة الدوال بشكل صحيح منطقياً، بل يجب أيضاً تقييد من يستطيع استدعاء كل دالة وتحت أي شروط. هنا تأتي أهمية Modifiers في Solidity كطبقة تنظيم وحماية تمنع الوصول غير المصرح به وتضمن تنفيذ الشروط قبل دخول جسم الدالة.
إذا كنت قد قرأت مقال الدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟ فستعرف أن أي دالة public أو external قد تكون قابلة للاستدعاء من أي عنوان. هذا السلوك خطير جداً إن كانت الدالة تعدّل حالة العقد أو تنقل أموالاً أو تغيّر صلاحيات. لذلك تُستخدم Modifiers لتطبيق قواعد وصول واضحة وقابلة لإعادة الاستخدام.
ما هو Modifier في Solidity؟
الـ Modifier هو كتلة منطقية تُربط بالدالة لكي يتم تنفيذ شرط أو أكثر قبل تنفيذ جسم الدالة، وأحياناً بعده. يمكن اعتباره بمثابة حارس برمجي يفحص السياق مثل المرسل الحالي msg.sender أو حالة العقد أو قيمة الوقت أو حالة الإيقاف.
يعتمد هذا المفهوم على الرمز _; داخل تعريف الـ modifier، وهو المكان الذي يُحقن فيه كود الدالة الأصلية. لذلك فالمعدل ليس مجرد شرط if مكرر، بل أداة تصميم تنظيمي مهمة تقلل التكرار وترفع الأمان والوضوح.
لماذا نحتاج إلى Modifiers في العقود الذكية؟
العقد الذكي يعمل في بيئة لا مركزية ومفتوحة، وأي مستخدم يستطيع محاولة استدعاء دواله إذا كانت مرئية على الشبكة. في هذا السياق، تصبح الحماية الداخلية للدوال جزءاً أساسياً من بنية الأمان وليس مجرد تحسين اختياري.
- منع المستخدمين غير المصرح لهم من تعديل البيانات الحساسة.
- فرض شروط تشغيل مثل تفعيل العقد أو إيقافه.
- إعادة استخدام قواعد الأمان على عدة دوال بدون تكرار.
- تسهيل قراءة الكود ومراجعته أثناء
Security Auditing.
ولفهم الخلفية العامة لبيئة Web3 يمكنك الرجوع إلى مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟، ثم البناء عليه عند تصميم صلاحيات العقود.
أشهر حالة استخدام: onlyOwner
أشهر معدل في Solidity هو onlyOwner، ويُستخدم للسماح لعنوان إداري واحد فقط بتنفيذ أوامر حساسة مثل تحديث الإعدادات أو سحب الأموال أو تعديل العناوين الموثوقة داخل العقد.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AdminControlledVault {
address public owner;
uint256 public totalDeposits;
bool public paused;
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
event Deposit(address indexed sender, uint256 amount);
event Withdraw(address indexed to, uint256 amount);
event PauseStatusChanged(bool status);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function deposit() external payable whenNotPaused {
require(msg.value > 0, "Zero value");
totalDeposits += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint256 amount) external onlyOwner {
require(amount <= address(this).balance, "Insufficient balance");
payable(owner).transfer(amount);
emit Withdraw(owner, amount);
}
function setPaused(bool _paused) external onlyOwner {
paused = _paused;
emit PauseStatusChanged(_paused);
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Invalid address");
address oldOwner = owner;
owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
في هذا المثال، لا يمكن استدعاء الدوال withdraw وsetPaused وtransferOwnership إلا من قبل المالك المخزن في المتغير owner. هكذا يتم عزل الوظائف الإدارية عن الاستخدام العام.
شرح بنية المعدل خطوة بخطوة
1) تعريف متغير الصلاحية
عادة تبدأ بتحديد الجهة المخولة، وغالباً تكون عنواناً من النوع address. إذا كنت قد راجعت أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables) فستلاحظ أن هذا المتغير يُحفظ ضمن حالة العقد ويُستخدم لاحقاً في المقارنة.
2) كتابة شرط التحقق
داخل الـ modifier نستخدم غالباً الدالة require للتحقق من الشرط. إذا فشل الشرط يتم إيقاف التنفيذ بالكامل وإرجاع رسالة خطأ، ما يمنع تعديل الحالة أو استهلاك غاز إضافي غير ضروري بعد نقطة الفشل.
3) إدراج _;
يمثل هذا السطر موضع تنفيذ جسم الدالة المرتبطة. كل ما يكتب قبله ينفذ أولاً، وكل ما بعده ينفذ لاحقاً. في معظم سيناريوهات التحكم بالوصول يوضع الشرط قبل _; مباشرة.
استخدام أكثر من Modifier لنفس الدالة
يمكن ربط عدة معدلات بدالة واحدة. هذه ميزة قوية تسمح ببناء سياسات مركبة مثل: يجب أن يكون المستدعي مالكاً، ويجب ألا يكون العقد متوقفاً، ويجب أن تكون القيمة ضمن مدى معين. هذا النمط أكثر نظافة من حشر الشروط داخل كل دالة.
function emergencyWithdraw(uint256 amount)
external
onlyOwner
whenNotPaused
{
require(amount > 0, "Invalid amount");
payable(owner).transfer(amount);
}
يُنفذ onlyOwner أولاً ثم whenNotPaused ثم جسم الدالة. لذلك يجب ترتيب المعدلات بوعي، خاصة إذا كان أحدها يقرأ أو يغيّر حالة معينة.
في مراجعات الأمان، من الأفضل أن تكون شروط الوصول في
Modifiersقصيرة وواضحة ومباشرة. التعقيد الزائد داخل المعدلات قد يُخفي ثغرات منطقية ويصعّب تتبع مسار التنفيذ أثناء التدقيق.
معدلات تقبل معاملات
يمكن للـ modifier أن يستقبل معاملات مثل الدوال تماماً. يفيد ذلك عندما تريد مقارنة عنوان معين أو حد رقمي معين بشكل مرن.
modifier onlyAddress(address allowed) {
require(msg.sender == allowed, "Sender not allowed");
_;
}
هذا النمط مفيد جداً عند بناء أنظمة أدوار بسيطة أو عند السماح لعقد خارجي محدد باستدعاء دوال داخلية، مثل Oracle أو عقد حوكمة أو جسر بين شبكات.
العلاقة بين Modifiers وباقي مكونات Solidity
المعدلات لا تعمل بمعزل عن بقية اللغة. فهي ترتبط مباشرة مع القواميس (Mappings): أسرع طريقة لربط عناوين المحافظ بأرصدتها (Key-Value) إذا كنت تريد بناء نظام صلاحيات متعدد المستخدمين، كما يمكن أن تقترن مع الأحداث (Events): كيف يخبر العقد الذكي واجهة الموقع (React) بأن شيئاً ما قد حدث؟ لتسجيل عمليات نقل الملكية أو الإيقاف الإداري.
كذلك تعتمد شروطها أحياناً على منطق مثل الجمل الشرطية (If/Else) وحلقات التكرار (For/While) داخل العقود الذكية، لكن يجب استخدام هذا المنطق بحذر حتى لا يتحول المعدل إلى نقطة تعقيد غير ضرورية.
أفضل الممارسات الأمنية وتحسين الغاز
لا تعتمد على اسم المعدل فقط مثل
onlyOwnerوتفترض أنه آمن دائماً؛ راجع كيف تم تعيينowner، وكيف يتم نقله، وهل توجد حماية من العنوان الصفريaddress(0).
لخفض استهلاك
Gas Fees، اجعل المعدلات تتحقق من الشروط بأقل عدد ممكن من عمليات القراءة من التخزينstorage. ويمكنك فهم أثر الكلفة بشكل أعمق عبر التكاليف (Gas Fees): كيف يحسب البلوكتشين تكلفة تنفيذ الأكواد؟.
- استخدم رسائل خطأ واضحة داخل
require. - تجنب وضع منطق تجاري ثقيل داخل المعدلات.
- افصل بين التحكم بالوصول ومنطق الأعمال كلما أمكن.
- اختبر حالات الفشل والنجاح في
HardhatأوRemix IDEقبل النشر. ويمكنك الرجوع إلى محرر Remix IDE: كتابة ونشر أول عقد ذكي (Smart Contract) على المتصفح مباشرة.
خاتمة
تمثل Modifiers إحدى أهم أدوات الحماية في Solidity لأنها تنقل شروط الوصول من داخل الدوال إلى طبقة تنظيمية قابلة لإعادة الاستخدام والمراجعة. وعندما تُستخدم بشكل صحيح، فهي تمنع التنفيذ غير المصرح به، وتوضح نية المطور، وتقلل التكرار، وتسهّل التدقيق الأمني.
باختصار، إذا كانت الدالة حساسة أو تؤثر في أرصدة أو صلاحيات أو إعدادات تشغيل، فلا تتركها مكشوفة. اربطها بمعدل مناسب مثل onlyOwner أو غيره، واختبر كل سيناريو بعناية قبل نشر العقد على أي EVM Testnet أو شبكة رئيسية.
24 comments