مشروع مصغر: بناء عقد ذكي لـ “حصالة إلكترونية” (Piggy Bank) تستقبل وتسحب الأموال
مشروع مصغر: بناء عقد ذكي لـ “حصالة إلكترونية” (Piggy Bank) تستقبل وتسحب الأموال
يُعد بناء عقد ذكي بسيط يستقبل الأموال ثم يسمح بسحبها في وقت لاحق من أفضل المشاريع التعليمية لفهم منطق Smart Contracts عملياً. هذا النموذج يوضح كيف يتعامل العقد مع Ether، وكيف تُفرض الصلاحيات، وكيف تُسجّل العمليات عبر Events.
إذا كنت قد قرأت سابقاً مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟ فستعرف أن هذا العقد يعمل فوق بيئة لا مركزية تُنفذ فيها التعليمات على EVM. أما من ناحية استقبال وإرسال الأموال، فالمفهوم مرتبط مباشرة بمقال استلام وإرسال الأموال (Ether) برمجياً: فهم الدوال payable و fallback.
فكرة العقد الذكي: كيف تعمل الحصالة الإلكترونية؟
الحصالة الإلكترونية هنا هي عقد ذكي يحتفظ بالأموال المرسلة إليه، مع تعيين مالك واحد فقط عند النشر. يمكن لأي مستخدم إرسال أموال إلى العقد، لكن السحب الكامل أو الجزئي يجب أن يكون محصوراً بصاحب الحصالة عبر منطق وصول واضح.
هذا النوع من العقود مفيد تعليمياً لأنه يجمع بين عدة مفاهيم أساسية:
- تعريف متغيرات الحالة
State Variables. - استخدام الدوال
payable. - حماية الوصول بواسطة
modifier. - إرسال الأموال باستخدام
call. - توثيق العمليات عبر الأحداث (Events): كيف يخبر العقد الذكي واجهة الموقع (React) بأن شيئاً ما قد حدث؟.
المتطلبات البرمجية قبل التنفيذ
قبل كتابة الكود، يُفضّل أن تكون بيئة التجربة جاهزة. يمكنك استخدام محرر Remix IDE: كتابة ونشر أول عقد ذكي (Smart Contract) على المتصفح مباشرة للتجربة السريعة، أو الاعتماد على Hardhat إذا أردت تطويراً منظماً واختبارات لاحقة.
ولكي تنشر العقد على شبكة اختبار، ستحتاج إلى محفظة متصلة مثل MetaMask. إذا لم تكن أعددتها بعد، راجع إعداد بيئة التطوير: تثبيت محفظة MetaMask والاتصال بشبكات الاختبار (Testnets) ثم احصل على رصيد تجريبي من الحصول على عملات تجريبية مجانية (Faucet) للبدء في نشر واختبار العقود الذكية.
كود عقد الحصالة الإلكترونية الكامل
فيما يلي نسخة عملية لعقد حصالة إلكترونية تدعم الإيداع، السحب الجزئي، السحب الكلي، وقراءة الرصيد الحالي. الكود يعتمد على مفاهيم من أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables) والدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PiggyBank {
address public owner;
event Deposit(address indexed sender, uint256 amount, uint256 newBalance);
event Withdrawal(address indexed owner, uint256 amount, uint256 remainingBalance);
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
constructor() {
owner = msg.sender;
}
receive() external payable {
require(msg.value > 0, "Send some ether");
emit Deposit(msg.sender, msg.value, address(this).balance);
}
function deposit() external payable {
require(msg.value > 0, "Amount must be greater than zero");
emit Deposit(msg.sender, msg.value, address(this).balance);
}
function getBalance() external view returns (uint256) {
return address(this).balance;
}
function withdraw(uint256 amount) external onlyOwner {
require(amount > 0, "Amount must be greater than zero");
require(amount <= address(this).balance, "Insufficient contract balance");
(bool success, ) = payable(owner).call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawal(owner, amount, address(this).balance);
}
function withdrawAll() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds available");
(bool success, ) = payable(owner).call{value: balance}("");
require(success, "Transfer failed");
emit Withdrawal(owner, balance, address(this).balance);
}
}
شرح مكونات العقد سطراً ومنطقاً
1) متغير المالك وتحديد الصلاحية
المتغير owner يُخزّن عنوان ناشر العقد. داخل constructor يتم تعيين msg.sender كمالك. هذا الأسلوب شائع جداً في عقود التحكم الإداري.
أما modifier onlyOwner فهو يطبق نمط حماية مباشر. وقد تناولنا الفكرة بتوسع في المعدلات (Modifiers): حماية الدوال برمجياً (مثل: السماح للمدير فقط بتنفيذ الأمر).
2) استقبال الأموال بطريقتين
العقد يسمح بالإيداع عبر الدالة deposit() أو عبر الدالة الخاصة receive(). الفرق أن receive تُستدعى عندما يُرسل المستخدم Ether مباشرة إلى عنوان العقد دون بيانات إضافية.
هذا التصميم مرن، لأنه يجعل العقد قادراً على استقبال الأموال سواء من واجهة أمامية أو من تحويل مباشر من محفظة. وفي الحالتين يتم التحقق من أن قيمة msg.value أكبر من صفر.
3) قراءة الرصيد بدون تكلفة تعديل
الدالة getBalance() معلنة كـ view لأنها تقرأ فقط من الحالة دون تعديلها. هذا يرتبط مباشرة بمقال أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas.
4) آلية السحب الآمنة
الدالتان withdraw() وwithdrawAll() تستخدمان call بدلاً من transfer لمرونة أكبر مع تغيّرات تكلفة الغاز في بيئة EVM.
قبل تنفيذ الإرسال، يتم التحقق من الرصيد والكمية المطلوبة عبر require. وإذا فشلت عملية الإرسال، يتم التراجع عن التنفيذ بالكامل. لمراجعة هذا النمط، انظر التعامل مع الأخطاء وإرجاع الأموال: استخدام require, assert, revert.
أمنياً، من الأفضل دائماً تطبيق مبدأ
Checks-Effects-Interactionsعند إرسال الأموال من العقد. في هذا المثال لا نعدّل أرصدة داخلية معقدة، لذلك المخاطر أقل، لكن في العقود الأكبر يجب الانتباه بشدة إلى هجماتReentrancy.
خطوات نشر واختبار العقد
- افتح
Remixأو مشروعHardhat. - أنشئ ملفاً باسم
PiggyBank.sol. - الصق الكود البرمجي ثم اختر نسخة مترجم متوافقة مع
pragma solidity ^0.8.20. - انشر العقد من محفظتك، وسيتم تسجيل عنوانك كقيمة للمتغير
owner. - اختبر الإيداع عبر
deposit()أو بتحويل مباشر للعقد. - استخدم
getBalance()للتأكد من تحديث الرصيد. - اختبر السحب الجزئي ثم السحب الكلي من حساب المالك فقط.
تحسينات متقدمة يمكن إضافتها لاحقاً
هذا المشروع مصغر، لكنه قابل للتوسعة بسهولة. مثلاً يمكن إضافة منطق زمني يمنع السحب قبل تاريخ معين، أو إنشاء سجل مساهمات لكل عنوان باستخدام mapping كما في القواميس (Mappings): أسرع طريقة لربط عناوين المحافظ بأرصدتها (Key-Value).
كذلك يمكن بناء نسخة جماعية من الحصالة تخزن بيانات كل مساهم باستخدام struct أو حتى قائمة مساهمين عبر array. هذا مناسب عندما يتحول المشروع لاحقاً إلى نموذج ادخار جماعي أو تمويل مصغر.
من زاوية
Gas Optimization، تجنب إضافة هياكل تخزين غير ضرورية إذا كان الهدف من العقد مجرد الادخار والسحب. كل كتابة إلى التخزينstorageترفع كلفة التنفيذ، وهو ما يرتبط عملياً بمقال التكاليف (Gas Fees): كيف يحسب البلوكتشين تكلفة تنفيذ الأكواد؟.
خاتمة
مشروع الحصالة الإلكترونية ليس مجرد مثال بسيط على استقبال الأموال، بل هو مدخل ممتاز لفهم دورة حياة الأموال داخل العقد الذكي، وآلية التحكم في الصلاحيات، وأهمية تسجيل الأحداث، والفرق بين القراءة والتعديل داخل Solidity.
وعندما تتقن هذا النموذج، ستصبح مستعداً للانتقال إلى عقود أكثر تعقيداً مثل الخزائن متعددة المستخدمين، الاشتراكات الدورية، أو تطبيقات الادخار المربوطة بواجهات DApps. الأهم هو أن تتعامل مع كل سطر برمجي بعقلية أمنية، لأن أي عقد يتعامل مع الأموال يجب أن يُكتب وكأنه منتج حقيقي لا مجرد تجربة تعليمية.
2 comments