كيفية بناء تطبيق التصويت المستندة إلى الرمز المميز على Ethereum

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

ليست جميع طلبات التصويت التي تستند إلى الرمز المميز هي نفسها ، ولكن معظمها يتبع اتفاقيات مماثلة. سير العمل بشكل عام يبدو مثل هذا:

  • يقدم المكون اقتراحًا
  • يمكن للناخبين الآخرين التصويت لصالح أو معارضة الاقتراح
  • بمجرد أن يصل الاقتراح إلى عتبة محددة مسبقًا ، يمر التصويت أو يفشل ويتم اتخاذ الإجراءات المناسبة المناسبة

يبدو سهلا بما فيه الكفاية ، أليس كذلك؟

ومع ذلك ، هناك تحديات تأتي مع تطبيق تطبيقات التصويت الموزعة المستندة إلى الرمز المميز. تزوير الناخبين في أعلى القائمة. هناك أيضًا تحديات تتعلق بكيفية إدارة الحالة وكيف ينبغي أن تعمل الخصائص الفريدة المرتبطة ببعض الرموز مع آليات الحوكمة المقابلة.

مع ذلك ، دعنا نلقي نظرة على كيفية إنشاء تطبيق تصويت موزع على أساس الرمز المميز على Ethereum. للقيام بذلك ، لنفترض أنك تخطط لإنشاء عقدين يتفاعلان مع بعضهما البعض:

  • عقد الرمز المميز الذي يحتوي على تعيين عناوين الحسابات وأرصدةها
  • عقد إدارة يستخدم لإدارة نظام التصويت

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

يقدم المكون اقتراحًا

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

  / / قم بتعيين معرف مقترح لمقترح محدد
  تعيين (uint => Proposal) المقترحات العامة ؛
  / / قم بتعيين معرّف الاقتراح إلى عنوان الناخب وتصويته
  mapping (uint => mapping (address => bool)) صوت الجمهور ؛
  // تحديد ما إذا كان المستخدم محظورًا من التصويت
  تعيين (address => uint) عام محظور ؛
  مقترح هيكلي
    uint votes تلقى؛
    مرت منطقي.
    مقدم العنوان ؛
    uint voteDeadline؛
  }
///dev يسمح لحامل الرمز المميز بتقديم اقتراح للتصويت عليه
  وظيفة SubmitProposal ()
    عامة
    onlyEligibleVoter (msg.sender)
    whenNotBlocked (msg.sender)
    إرجاع (uint proposID)
  {
    votesReceived = token.balanceOf (msg.sender) ؛
    proposID = addProposal (votesReceived) ؛
    تنبعث منها ProposalSubmitted (proposID) ؛
    عودة المقترح
  }

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

معدل فقط مؤهل للتصويت (عنوان _ مصوت)
   balance = token.balanceOf (_voter)؛
   يتطلب (الرصيد> 0) ؛
  _؛
}

بمجرد أن نقرر أن الناخب مؤهل ، فإن وظيفة submitProposal ستقوم بتعيين رصيد الرمز المميز لمقدمي الطلبات إلى تصويت متغير. سنقوم بعد ذلك بتمرير هذه القيمة كوسيطة لوظيفة addProposal لدينا:

///dev يضيف اقتراحًا جديدًا لتعيين الاقتراح
///param _votesReceived من المستخدم تقديم الاقتراح
  وظيفة addProposal (uint _votesReceived)
   داخلي
   إرجاع (uint proposID)
  {
   من الأصوات = _ التصويتات ؛
   إذا (الأصوات <الأصوات المطلوبة) {
      if (proposIDcount == 0) {
        proposIDcount = 1؛
      }
    proposID = proposIDcount؛
    المقترحات [proposID] = الاقتراح ({
    الأصواتتلقى: الأصوات ،
    مرت: كاذبة ،
    مقدم: msg.sender ،
    VoteDeadline: الآن + تصويت طول
     })؛
    تم حظره [msg.sender] = proposID؛
    صوّت [proposID] [msg.sender] = صواب ؛
    proposIDcount = proposIDcount.add (1)؛
    عودة المقترح
   }
   آخر
    يتطلب (token.balanceOf (msg.sender)> = من الأصوات).
    endVote (proposalID)؛
    عودة المقترح
   }
  }

تقوم وظيفة addProposal بإنشاء معرف فريد للاقتراح. سيؤدي أيضًا إلى إنشاء كائن aProposal يمكن استخدامه لمراقبة عدد الأصوات التي تلقاها الاقتراح ، سواء تم اجتيازه أم لا ، وموعد التصويت للاقتراح.

بعد إضافة الاقتراح ، ستُرجع الدالة submitProposal معرِّف الاقتراح الفريد وتصدر حدثًا ProposalSubmitted لكي تستهلكه الواجهة الأمامية.

يمكن للناخبين الآخرين التصويت لصالح أو معارضة الاقتراح

بعد ذلك ، نحتاج إلى تطوير وظيفة لمعالجة أصوات الناخبين الذين يرغبون في التصويت لصالح اقتراح معين.

في هذه الوظيفة ، لدينا أولاً عبارة if التي نستخدمها لتحديد ما إذا كان قد تم منع ناخب معين من التصويت (المزيد حول هذا لاحقًا). إذا لم يتم حظر أحد الناخبين ، فنحن نحدد ما هو رصيدهم المميز ونضيف تلك القيمة إلى المتغير المستلم من أصوات المقترحات المحددة.

///dev يسمح لحاملي الرمز المميز بتقديم أصواتهم لصالح مقترح محدد
///param _proposalID معرّف الاقتراح الذي يقوم حامل الرمز المميز بالتصويت عليه
  
  وظيفة submitVote (uint _proposalID)
    onlyEligibleVoter (msg.sender)
    whenNotBlocked (msg.sender)
    عامة
    عوائد (منطقي)
  {
    ذاكرة الاقتراح ع = المقترحات [_proposalID] ؛
    if (محظور [msg.sender] == 0) {
      محظور [msg.sender] = _proposalID؛
    } آخر إذا (p.votingDeadline> المقترحات [محظور [msg.sender]].
    {
// الموعد النهائي للتصويت لهذا الاقتراح أبعد من المستقبل
// الاقتراح الذي يحظر المرسل ، لذلك اجعله مانعًا
      محظور [msg.sender] = _proposalID؛
    }
    votesReceived = token.balanceOf (msg.sender) ؛
    المقترحات [_proposalID] .votesReceived + = votesReceived؛
    تصويت [_proposalID] [msg.sender] = صواب ؛
    إذا (مقترحات [_proposalID] .votesReceived> = من الأصوات)
    {
      المقترحات [_proposalID] .passed = true؛
      تنبعث منها الأصوات المقدمة (
        _proposalID،
        votesReceived،
        مقترحات [_proposalID] .passed
      )؛
      endVote (_proposalID)؛
    }
    تنبعث منها الأصوات المقدمة (
      _proposalID،
      votesReceived،
      مقترحات [_proposalID] .passed
    )؛
    العودة الحقيقية ؛
  }

نحن نتحقق بعد ذلك من معرفة ما إذا كان الاقتراح قد تمت نتيجة الأصوات الجديدة التي حصل عليها. إذا كان الأمر كذلك ، فسنرسل حدثًا VotesSubmitted وسنتصل بوظيفة endVote لدينا. إذا لم ينجح ذلك ، فسنبعث ببساطة حدث VotesSubmitted وسنعود صادقة للاعتراف بأن الأصوات قد تم استلامها ومعالجتها بنجاح.

تم التصويت بنجاح واتخاذ الإجراءات المناسبة المناسبة

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

في وظيفة submitVote الخاصة بنا ، سوف تتذكر هذا إذا كان البيان الذي يتحقق لمعرفة ما إذا كان قد تم تلقي أصوات كافية لاقتراح تمرير:

إذا (مقترحات [_proposalID] .votesReceived> = من الأصوات)
    المقترحات [_proposalID] .passed = true؛
    تنبعث منها الأصوات المقدمة (
       _proposalID،
       votesReceived،
       مقترحات [_proposalID] .passed
    )؛
    endVote (_proposalID)؛
   }

إذا حصلت على عدد كافٍ من الأصوات ، فسيتم استدعاء وظيفة endVote التي تبدو كما يلي:

///dev يحدد متى سينتهي تصويت معين
///param _proposalID معرف الاقتراح الخاص
  الدالة endVote (uint _proposalID)
    داخلي
  {
    تتطلب (voteSuccessOrFail (_proposalID))؛
    updateProposalToPassed (_proposalID)؛
  }

ستلاحظ أننا نستخدم بيانًا مطلوبًا لضمان حصول مقترح معين على عدد كاف من الأصوات لتمريره جنبًا إلى جنب مع وظيفة تصويتنا - AccessOrFail:

///dev لتحديد ما إذا كان تصويت معين قد نجح أم لا
///param _proposalID معرف الاقتراح للتحقق
///return إرجاع ما إذا كان تصويت معين قد نجح أو لم ينجح
  وظيفة التصويتسوينيسفيلي (uint _proposalID)
    عامة
    رأي
    عوائد (منطقي)
  {
    إرجاع المقترحات [_proposalID] .passed؛
  }

إذا نجح التصويت ، فسنقوم باستدعاء وظيفة تقوم بتحديث متغير تمرير الاقتراح إلى صحيح.

كيفية منع تزوير الناخبين

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

إذا كنت ستتذكر من وقت سابق ، فقد صادفنا تعيينًا اسمه محظورًا.

// تحديد ما إذا كان المستخدم محظورًا من التصويت
  تعيين (address => uint) عام محظور ؛

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

لتنفيذ ذلك في عقدنا المميز ، سنستخدم المُعدّل التالي عند عدم الحظر:

///dev Modifier للتحقق مما إذا كان قد تم حظر حساب مستخدم من إجراء التحويلات
    معدل عند عدم حجب (عنوان _account) {
      تتطلب (governance.isBlocked (_account)!)؛
      _؛
    }

بعد ذلك ، سوف نستخدم المُعدّل في عقد الرمز المميز الخاص بنا لنقله ونقله والذي يرث من عقد الرمز المميز لـ OpenZeppelin الخاص بـ ERC-20.

 نقل الوظيفة (عنوان إلى ، uint256 القيمة)
      عامة
      whenNotBlocked (msg.sender)
      عوائد (منطقي)
    {
      إرجاع super.transfer (إلى ، القيمة) ؛
    }
نقل وظيفةمن (عنوان من ، عنوان إلى ، قيمة uint256)
      عامة
      whenNotBlocked (من)
      عوائد (منطقي)
    {
      إرجاع super.transferFrom (من ، إلى ، القيمة) ؛
    }

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

الوجبات السريعة

  • ليست هناك طريقة "صحيحة" واحدة لإنشاء طلب تصويت قائم على الرمز. سوف يعتمد على حالة الاستخدام.
  • تتبع معظم طلبات التصويت القائمة على الرمز نمطًا كالتالي: يقدم المكون التأسيسي مقترحًا ؛ حيث يمكن للناخبين الآخرين التصويت لصالح أو معارضة الاقتراح - بمجرد أن يصل الاقتراح إلى عتبة محددة مسبقًا ، يمر التصويت أو يفشل ويتم اتخاذ الإجراءات المناسبة المناسبة .
  • يعد تزوير الناخبين تحديا كبيرا للتصدي له عندما يتعلق الأمر بتطبيق تطبيقات التصويت الموزعة التي تعتمد على الرمز المميز. يجعل fungability من الرموز ERC-20 من الصعب منع هذا.
  • هناك أيضًا تحديات يجب مراعاتها مرتبطة بكيفية إدارة الحالة. بمعنى ، كيف ينبغي أن تعمل الخصائص الفريدة المرتبطة ببعض الرموز جنبا إلى جنب مع آليات الحوكمة المقابلة
  • في نهاية المطاف ، التصويت القائم على الرمز المميز هو آلية حوكمة قوية للتطبيقات المضطربة ، ولكنها تنطوي على مخاطر تتطلب التخفيف.

هل أنت مهتم بمعرفة المزيد عن تطوير Ethereum؟