المنظمات المستقلة اللامركزية (DAOs): كيف تبرمج نظام تصويت إلكتروني غير قابل للتزوير؟
المنظمات المستقلة اللامركزية (DAOs): كيف تبرمج نظام تصويت إلكتروني غير قابل للتزوير؟
تُعد DAOs من أهم تطبيقات Blockchain لأنها تنقل الحوكمة من الإدارة المركزية إلى قواعد برمجية شفافة تُنفذ داخل Smart Contracts. الفكرة ليست مجرد تصويت رقمي، بل بناء آلية قرار يمكن التحقق منها علناً، ولا يستطيع المشرف تعديل نتائجها بعد نشر العقد.
إذا كنت قد قرأت مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟ فستلاحظ أن القيمة الحقيقية هنا تأتي من السجل غير القابل للتلاعب، حيث تُخزن الأصوات والمعايير الزمنية والنتائج على السلسلة نفسها، لا في قاعدة بيانات يمكن لمسؤول النظام العبث بها.
في هذا المقال سنبني منطق نظام تصويت أساسي لمنظمة لامركزية، مع شرح البنية البرمجية، إدارة المقترحات، منع التصويت المزدوج، احتساب النصاب، وإعلان النتيجة. كما سنشير إلى اعتبارات الأمان والتكلفة حتى يكون النموذج قابلاً للتطوير لاحقاً داخل بيئة Hardhat وربطه بواجهة عبر Ethers.js.
ما الذي يجعل التصويت على السلسلة غير قابل للتزوير؟
في الأنظمة التقليدية، تخزن الأصوات عادةً داخل خادم مركزي. هذا يعني أن الثقة تُمنح لمسؤول قاعدة البيانات أو مزود الخدمة. أما في نموذج On-Chain Voting فكل صوت هو معاملة موقعة من محفظة المستخدم، ويمكن التحقق منها تشفيرياً عبر المفتاح العام.
لفهم هذه النقطة برمجياً بشكل أعمق، يفيد الرجوع إلى التشفير والمفاتيح: كيف تعمل المحافظ الرقمية (Public & Private Keys) برمجياً؟ لأن هوية المصوّت في البلوكشين لا تعتمد على اسم مستخدم وكلمة مرور، بل على توقيع مشفر لا يمكن تقليده دون امتلاك المفتاح الخاص.
- كل تصويت يُسجل كمعاملة مرتبطة بعنوان محفظة.
- العقد الذكي يفرض القواعد تلقائياً باستخدام
require. - السجل شفاف ويمكن لأي طرف مراجعته بعد انتهاء التصويت.
- لا يمكن تغيير النتيجة دون تغيير حالة العقد نفسها، وهو أمر مرئي ومكلف وخاضع لإجماع الشبكة.
تصميم نموذج الحوكمة داخل العقد الذكي
قبل كتابة الكود، نحتاج إلى تحديد عناصر النظام. كل DAO تحتاج عادةً إلى أعضاء، مقترحات، فترة تصويت، وعدّاد للأصوات المؤيدة والمعارضة. هذا يعتمد على مفاهيم شرحتها مقالات مثل الهياكل (Structs) والقواميس (Mappings) والدوال (Functions) في Solidity.
سنستخدم بنية بسيطة:
- خريطة تحدد من هو عضو يملك حق التصويت.
- هيكل بيانات يمثل المقترح.
- خريطة فرعية لتسجيل ما إذا كان العضو قد صوّت على مقترح محدد.
- دوال لإنشاء المقترحات والتصويت وتنفيذ النتيجة.
العقد الذكي الأساسي للتصويت
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleDAO {
address public admin;
uint256 public proposalCount;
uint256 public minimumQuorum;
struct Proposal {
uint256 id;
string description;
uint256 yesVotes;
uint256 noVotes;
uint256 deadline;
bool executed;
}
mapping(address => bool) public members;
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
event MemberAdded(address indexed member);
event ProposalCreated(uint256 indexed proposalId, string description, uint256 deadline);
event Voted(uint256 indexed proposalId, address indexed voter, bool support);
event ProposalExecuted(uint256 indexed proposalId, bool passed);
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
modifier onlyMember() {
require(members[msg.sender], "Not a DAO member");
_;
}
constructor(address[] memory initialMembers, uint256 _minimumQuorum) {
admin = msg.sender;
minimumQuorum = _minimumQuorum;
for (uint256 i = 0; i < initialMembers.length; i++) {
members[initialMembers[i]] = true;
emit MemberAdded(initialMembers[i]);
}
}
function addMember(address member) external onlyAdmin {
require(member != address(0), "Zero address");
require(!members[member], "Already member");
members[member] = true;
emit MemberAdded(member);
}
function createProposal(string calldata description, uint256 durationInSeconds)
external
onlyMember
{
require(bytes(description).length > 0, "Empty description");
require(durationInSeconds > 0, "Invalid duration");
proposalCount++;
proposals[proposalCount] = Proposal({
id: proposalCount,
description: description,
yesVotes: 0,
noVotes: 0,
deadline: block.timestamp + durationInSeconds,
executed: false
});
emit ProposalCreated(proposalCount, description, block.timestamp + durationInSeconds);
}
function vote(uint256 proposalId, bool support) external onlyMember {
Proposal storage proposal = proposals[proposalId];
require(proposal.id != 0, "Proposal not found");
require(block.timestamp < proposal.deadline, "Voting ended");
require(!hasVoted[proposalId][msg.sender], "Already voted");
hasVoted[proposalId][msg.sender] = true;
if (support) {
proposal.yesVotes++;
} else {
proposal.noVotes++;
}
emit Voted(proposalId, msg.sender, support);
}
function executeProposal(uint256 proposalId) external onlyMember {
Proposal storage proposal = proposals[proposalId];
require(proposal.id != 0, "Proposal not found");
require(block.timestamp >= proposal.deadline, "Voting still active");
require(!proposal.executed, "Already executed");
uint256 totalVotes = proposal.yesVotes + proposal.noVotes;
require(totalVotes >= minimumQuorum, "Quorum not reached");
proposal.executed = true;
bool passed = proposal.yesVotes > proposal.noVotes;
emit ProposalExecuted(proposalId, passed);
}
function getProposal(uint256 proposalId)
external
view
returns (
uint256 id,
string memory description,
uint256 yesVotes,
uint256 noVotes,
uint256 deadline,
bool executed
)
{
Proposal memory proposal = proposals[proposalId];
return (
proposal.id,
proposal.description,
proposal.yesVotes,
proposal.noVotes,
proposal.deadline,
proposal.executed
);
}
}
كيف يعمل هذا العقد خطوة بخطوة؟
المتغير members عبارة عن mapping يحدد من يملك حق الوصول إلى دوال الحوكمة. أما Proposal فهو struct يجمع بيانات المقترح في وحدة منطقية واحدة.
الدالة createProposal تنشئ مقترحاً جديداً مع موعد انتهاء محسوب عبر block.timestamp. بعد ذلك تسمح الدالة vote لكل عضو بالتصويت مرة واحدة فقط.
يتم منع التكرار عبر الخريطة الثنائية hasVoted[proposalId][msg.sender]. هذه واحدة من أبسط وأقوى الطرق لمنع التصويت المزدوج، وتستفيد من كفاءة التخزين التي ناقشناها في مقال القواميس (Mappings).
أخيراً، الدالة executeProposal لا تغيّر نتيجة الأصوات نفسها، لكنها تُغلق المقترح منطقياً بعد التحقق من انتهاء المهلة وتحقيق النصاب minimumQuorum.
تحسين النموذج من حوكمة بسيطة إلى حوكمة إنتاجية
العقد السابق تعليمي ومفيد، لكنه ليس الحد النهائي. في الأنظمة المتقدمة، لا يكون لكل عضو صوت واحد بالضرورة، بل قد يُحسب الوزن بحسب رصيد رمز حوكمة من نوع ERC-20. هنا يصبح التصويت Token-Weighted بدلاً من نموذج العضوية المباشرة.
كما يمكن توسيع النظام ليشمل:
- تفويض التصويت
Delegation. - تنفيذ قرارات مالية بعد النجاح، مثل تحويل أموال الخزينة.
- ربط التنفيذ بعقود أخرى عبر ما شرحناه في التفاعل بين العقود الذكية.
- الاعتماد على مكتبات آمنة مثل OpenZeppelin لتقليل أخطاء التطوير اليدوي.
لا تجعل دالة تنفيذ المقترح ترسل
Etherأو تستدعي عقوداً خارجية قبل تحديث حالة المقترح داخلياً. هذا يقلل مخاطر هجماتReentrancyالتي تم شرحها في أمن العقود الذكية (1): ثغرة إعادة الدخول، مع تطبيق الحماية في الحماية من ثغرة Reentrancy باستخدام ReentrancyGuard.
تقليل استهلاك الغاز في أنظمة التصويت
أي نظام حوكمة ناجح يجب أن يراعي التكلفة، لأن كثرة المشاركين تعني عدداً أكبر من المعاملات. إذا لم يكن التصميم فعالاً، ستصبح رسوم المشاركة مرتفعة وتضعف اللامركزية عملياً. لهذا من المهم فهم التكاليف (Gas Fees): كيف يحسب البلوكتشين تكلفة تنفيذ الأكواد؟.
- استخدم
calldataفي المدخلات النصية متى أمكن. - تجنب الحلقات الطويلة على قوائم أعضاء كبيرة داخل الدوال القابلة للتنفيذ.
- اعتمد على
eventsلتغذية الواجهة بدلاً من تكرار التخزين المكلف، كما في الأحداث (Events). - افصل دوال القراءة
viewعن دوال الكتابة، وراجع أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas.
إذا كان عدد الأعضاء كبيراً، فتجنب تصميم دالة تقوم بالمرور على جميع المحافظ لحساب النتيجة أو النصاب وقت التنفيذ. الأفضل تخزين العدادات تدريجياً أثناء التصويت، لأن الحلقات غير المحدودة قد تؤدي إلى فشل المعاملة بسبب استهلاك
Gasبشكل مفرط.
اختبار العقد وربطه بواجهة استخدام
بعد كتابة العقد، لا يكفي نشره مباشرة. يجب اختبار السيناريوهات الأساسية: عضو يصوّت مرتين، تصويت بعد انتهاء المهلة، تنفيذ قبل النصاب، وتنفيذ صحيح بعد الفوز. يمكنك تنفيذ ذلك داخل اختبارات الوحدة (Unit Tests) باستخدام Chai & Mocha ثم نشره لاحقاً كما في أتمتة نشر العقود.
وعلى مستوى الواجهة، يمكن عرض المقترحات ونتائجها بالاعتماد على قراءة البيانات من البلوكتشين وعرضها في واجهة الموقع مجاناً، ثم إرسال التصويت عبر كتابة البيانات وإرسال المعاملات من واجهة الويب إلى العقد الذكي. أما التحديث الفوري للحالة فيمكن إنجازه عبر الاستماع إلى الأحداث (Events) وتحديث واجهة React لحظياً.
خلاصة هندسية
برمجة نظام تصويت إلكتروني غير قابل للتزوير داخل DAO لا تعني فقط جمع أصوات Yes وNo. التحدي الحقيقي هو تصميم قواعد عادلة، قابلة للتدقيق، تمنع التكرار، وتحترم حدود الأمان والتكلفة على الشبكة.
النموذج الذي بنيناه يمثل أساساً ممتازاً لفهم الحوكمة اللامركزية عملياً. ومنه يمكنك التدرج إلى أنظمة أكثر تقدماً تشمل رموز الحوكمة، التفويض، الخزائن متعددة التوقيع، والتنفيذ الآلي للقرارات. وعندما تُبنى هذه المكونات بعناية، تتحول DAOs من فكرة نظرية إلى بنية مؤسسية رقمية يصعب تزويرها أو احتكارها.
3 comments