استاندارد ERC20 Votes در سالیدیتی

در این بخش، فرض می‌کنیم که خواننده با مفهوم اسنپ شات در ERC20 آشنایی دارد. اگر این‌طور نیست، ابتدا مقاله‌ «Snapshot در ERC20» را مطالعه کنید تا با موضوع آشنا شوید. استاندارد ERC20 Votes وظیفه اجرای رای گیری را بر عهده ندارد. این استاندارد در اصل یک توکن ERC20 است که از اسنپ شات و واگذاری حق رای پشتیبانی می‌کند. فرآیند رای گیری معمولاً توسط قراردادهای حاکمیتی انجام می‌شود.

در سیستم واگذاری (Delegation)، یک آدرس می‌تواند قدرت رای خود را بدون انتقال توکن ها به آدرس دیگری بسپارد.

برخلاف ERC20 Snapshot، این استاندارد از موجودی توکن ها اسنپ شات نمی‌گیرد. در عوض، قدرت رای هر آدرس را در نقاط مختلف ثبت می‌کند.

مزایای این طراحی

  • مشارکت غیرفعال: این سیستم به دارندگان توکن که مشارکت مستقیمی ندارند اجازه می‌دهد در تصمیم‌گیری‌های جمعی حضور داشته باشند. به بیان دیگر، حتی بدون رای دادن مستقیم هم می‌توان تأثیرگذار بود.

  • صرفه‌جویی در هزینه‌ها: در شرایطی که حساب های با موجودی کم بخواهند رای دهند، نیازی به پرداخت گس نیست. چرا که نماینده‌ای به جای آن‌ها رای می‌دهد. در نتیجه، این کار باعث صرفه جویی قابل توجهی در سطح کل شبکه می‌شود. به عنوان مثال، اگر هزار کاربر رای خود را به پنج نفر بسپارند، تنها پنج رای روی بلاک‌چین ثبت خواهد شد.

  • جلوگیری از رای تکراری: یکی از مهم‌ترین مزایای این ساختار، جلوگیری از دوباره‌کاری در رای گیری است. به همین دلیل، ثبت لحظه‌ای یا همان Checkpoint نقش مهمی در امنیت و دقت فرآیند رای گیری دارد.

ساختار ERC20 Votes در سالیدیتی

این استاندارد از سه استاندارد دیگر ارث بری می‌کند:

  • ERC20

  • ERC6372

  • ERC5805

به همین دلیل، توکنی که با استاندارد ERC20 Votes ساخته می‌شود، تمام قابلیت‌های توکن ERC20 را در اختیار دارد. همچنین ویژگی‌هایی را ارائه می‌دهد که مربوط به رای گیری و ثبت لحظه‌ای قدرت رای هستند.

عملکرد استاندارد ERC5805

تابع getVotes(address delegate)

این تابع، آدرس یک حساب را دریافت می‌کند و میزان کل قدرت رای آن را برمی‌گرداند. نکته مهم این است که مقدار بازگشتی ممکن است از balanceOf(delegate) بیشتر باشد. دلیل آن این است که آدرس‌های دیگر ممکن است قدرت رای خود را به این نماینده واگذار کرده باشند.

تابع delegate(address delegatee)

این تابع به msg.sender اجازه می‌دهد که قدرت رای خود را به آدرس delegatee واگذار کند. پس از انجام این کار، نماینده مذکور می‌تواند به جای فرستنده رای دهد.
در این فرآیند، هیچ توکنی منتقل نمی‌شود و تنها قدرت رای جابجا می‌شود.

همچنین، اگر کاربر بخواهد نماینده خود را تغییر دهد یا واگذاری را لغو کند، کافی است دوباره همین تابع را با آدرس جدید یا مقدار address(0) فراخوانی کند. این کار در هر زمان ممکن است.

نکات مهم درباره واگذاری رای:

  • واگذاری به‌صورت کلی انجام می‌شود. به بیان دیگر، کاربر نمی‌تواند تنها بخشی از قدرت رای خود را واگذار کند. همه یا هیچ.

  • از طرفی، اگر یک آدرس بخواهد رای خودش را استفاده کند، ابتدا باید قدرت رای را به خودش واگذار کند.
    این رفتار خاص به دلایل بهینه سازی مصرف گس در شبکه طراحی شده است.

تابع delegates(address account)

این تابع مشخص می‌کند که آدرس واردشده، قدرت رای خود را به کدام حساب واگذار کرده است. یعنی اگر کاربری رای خود را به شخص دیگری سپرده باشد، این تابع آدرس آن نماینده را برمی‌گرداند.

تابع delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

تابع delegateBySig به کاربر این امکان را می‌دهد که بدون پرداخت کارمزد (Gasless)، قدرت رای خود را به نماینده‌ای واگذار کند. در این حالت، حساب دیگری هزینه گس را پرداخت کرده و تراکنش را اجرا می‌کند.

این ویژگی، کاربردهای زیادی در طراحی سیستم‌های رای گیری غیرمتمرکز دارد؛ چرا که اجازه می‌دهد افراد بدون صرف هزینه، در فرآیند تصمیم‌گیری شرکت کنند.

پارامتر expiry زمان انقضای اعتبار این واگذاری را مشخص می‌کند. همچنین پارامترهای v، r و s اجزای امضای دیجیتال بیضوی (Elliptic Curve Digital Signature) هستند که برای احراز هویت و تأیید تراکنش استفاده می‌شوند.

نکته مهم اینجاست که این امضا باید با فرمت استاندارد EIP-712 تولید شده باشد. درون قرارداد، یک مقدار nonce برای هر آدرس نگهداری می‌شود که پس از هر تراکنش موفق، مقدار آن افزایش پیدا می‌کند. در بخش بعدی درباره نقش nonce بیشتر توضیح خواهیم داد.

تابع nonces(address account)

برای جلوگیری از Replay Attack، هر امضا نیاز به یک مقدار nonce دارد. این تابع مقدار nonce مربوط به یک آدرس را برمی‌گرداند. از این طریق، امضاکننده می‌تواند متوجه شود که در مرحله بعد باید کدام مقدار را امضا کند.

تابع getPastVotes(account, timepoint)

همان‌طور که در مقاله «Snapshot در ERC20» توضیح دادیم، اگر موجودی حساب ها در زمان‌های مشخص ذخیره نشود، امکان حمله رای دهی تکراری (Double Voting) وجود دارد. در این شرایط، قرارداد رای گیری به داده‌هایی که در گذشته ثبت شده‌اند مراجعه می‌کند تا تصمیم‌گیری انجام دهد.

با این حال، در ERC20 Votes فرآیند اسنپ شات مانند ERC20 Snapshot عمل نمی‌کند. در اینجا، اسنپ شات به‌صورت خودکار و برای هر حساب به‌طور مستقل ثبت می‌شود، نه از طریق فراخوانی مستقیم تابع snapshot.

ثبت اسنپ شات تنها در زمان وقوع یکی از چهار رویداد زیر انجام می‌شود:

  • صدور توکن (minting)

  • سوزاندن توکن (burning)

  • انتقال توکن (transfer)

  • واگذاری حق رای (delegation)

با وقوع هر یک از این رویدادها، یک struct شامل قدرت رای و زمان آن ایجاد می‌شود و به آرایه‌ای افزوده می‌شود که تاریخچه رای آن کاربر را نگه‌داری می‌کند.

از طرفی، درست مانند ERC20 Snapshot، استاندارد ERC20 Votes هم برای دسترسی به مقدار مربوط به یک زمان خاص از جستجوی دودویی (Binary Search) روی آرایه زمان ها استفاده می‌کند. در نتیجه، قرارداد اولین Checkpoint پس از زمان موردنظر را پیدا کرده و قدرت رای مربوط به همان زمان را بازمی‌گرداند.

نکته مهم: برخلاف ERC20 Snapshot که دارای شناسه سراسری برای اسنپ شات‌هاست، در ERC20 Votes چنین چیزی وجود ندارد. در اینجا اگر بخواهید داده ای از گذشته را بررسی کنید، باید زمان (timestamp) یا شماره بلاک (block number) موردنظر را مشخص کرده و آن را به‌عنوان timepoint به تابع بدهید.

رویدادها (Events)

استاندارد ERC5805 شامل دو رویداد (event) کلیدی است که نام آن‌ها کاملاً بیانگر عملکردشان است:

  • DelegateChanged

  • DelegateVotesChanged

این دو رویداد هنگام تغییر نماینده رای و همچنین تغییر در میزان قدرت رای نماینده ثبت می‌شوند.

ERC5805 یک واسط است، نه یک توکن

اگرچه در این مقاله درباره ERC20 Votes صحبت می‌کنیم، اما باید به یک نکته مهم اشاره کنیم: ERC5805 الزامی به پیاده سازی بر روی توکن‌ های قابل‌ تعویض (Fungible Tokens) ندارد.

در واقع، این استاندارد صرفاً یک interface یا واسط برنامه نویسی است. به همین دلیل، می‌توان از آن در کاربردهای متنوعی استفاده کرد. برای مثال، ممکن است در یک پروژه به‌جای توکن، از NFT استفاده شود. همچنین، گاهی یک نهاد متمرکز مسئول اختصاص دادن رای ها به آدرس‌هاست.

در چنین حالتی، توسعه دهنده می‌تواند از ERC5805 برای ثبت تاریخچه تغییرات قدرت رای استفاده کند؛ بدون اینکه الزاماً با توکن‌ های استاندارد ERC20 یا ERC721 سر و کار داشته باشد. این قابلیت به حفظ یک سابقه تغییرناپذیر (Immutable History) از نحوه توزیع قدرت رای کمک می‌کند.

استاندارد ERC6372

در بسیاری از موارد، فرض بر این است که قرارداد هوشمند برای ثبت زمان Checkpoint ها از block.timestamp استفاده می‌کند. با این حال، همه قراردادها از این روش تبعیت نمی‌کنند. برخی از آن‌ها ممکن است به‌جای آن از block.number یا حتی تابعی یکنوا (monotonically increasing) که وابسته به این متغیرهای جهانی باشد، استفاده کنند.

برای همین، ERC6372 این امکان را فراهم می‌کند که بفهمیم قرارداد از چه معیاری برای اندازه‌گیری زمان استفاده می‌کند. این استاندارد دو تابع کلیدی دارد:

تابع clock()

این تابع یک مقدار از نوع uint48 بازمی‌گرداند. خروجی آن ممکن است block.number، block.timestamp یا تابعی وابسته به آن‌ها باشد.

نوع داده uint48 به‌صورت خاص انتخاب شده است، چون می‌تواند تمام مقادیر معنادار مرتبط با زمان یا شماره بلاک را بدون مشکل ذخیره کند. این نوع داده آن‌قدر ظرفیت دارد که بتواند شماره بلاک ها را تا آینده‌ای بسیار دور، حتی فراتر از بازه زمانی تاریخ ثبت شده انسان، پوشش دهد.

تابع CLOCK_MODE()

در نگاه اول، استفاده از یک تابع با حروف بزرگ و نام‌گذاری به سبک Snake Case (مانند CLOCK_MODE) در سالیدیتی عجیب به نظر می‌رسد. با این حال، این همان روشی است که استاندارد EIP به‌طور مشخص تعیین کرده است.

این تابع یک رشته متنی (string) بازمی‌گرداند که نوع «واحد زمانی» مورد استفاده در تابع clock() را مشخص می‌کند.

اگر قرارداد از timestamp به‌عنوان ساعت استفاده کند، خروجی این تابع به صورت زیر خواهد بود:

اما اگر ساعت قرارداد بر اساس شماره بلاک باشد (block.number)، خروجی به شکل زیر خواهد بود:

این یعنی قرارداد از block.number استفاده می‌کند و از ابتدا (یعنی بلاک صفر زنجیره فعلی) شروع شده است.

اگر زمان شروع قرارداد از یک بلاک خاص در شبکه ای مشخص باشد، باید chain ID و شماره آن بلاک را نیز ارائه دهید.

برای مثال، اگر قرارداد روی شبکه Avalanche که شناسه زنجیره‌ آن 43114 است اجرا شود و ساعت آن از بلاک 100 شروع شود، خروجی تابع CLOCK_MODE() به صورت زیر خواهد بود:

این استاندارد هیچ رویدادی (event) تعریف نمی‌کند. برای مطالعه جزئیات بیشتر، پیشنهاد می‌شود به متن رسمی EIP مراجعه کنید که نسبت به بسیاری از پیشنهادها، خوانایی بالایی دارد. همچنین لازم به ذکر است که این EIP هنوز نهایی نشده و در وضعیت پیشنهادی قرار دارد.

جمع‌بندی تفاوت های استاندارد ERC20 Votes و ERC20 Snapshot

در این بخش، تفاوت های اصلی بین دو استاندارد ERC20 Votes و ERC20 Snapshot به‌طور خلاصه مقایسه شده‌اند:

مفهوم زمان (Notion of Time)

  • ERC20 Votes به‌طور شفاف و مستقیم مفهوم زمان را در ساختار خود وارد کرده است. این استاندارد از زمان (timestamp) یا شماره بلاک (block number) به‌عنوان «ساعت» استفاده می‌کند.

  • در مقابل، ERC20 Snapshot از یک شمارنده افزایشی استفاده می‌کند که به‌صورت غیرمستقیم با گذر زمان تغییر می‌کند. این شمارنده به‌عنوان شناسه اسنپ شات عمل می‌کند.

چه چیزی ثبت می‌شود؟

  • در ERC20 Votes، قدرت رای (Voting Power) کاربران در هر نقطه زمانی ثبت می‌شود.

  • اما در ERC20 Snapshot، موجودی توکن ها (Token Balances) در هر اسنپ شات ذخیره می‌شود.

چه زمانی Checkpoint ها به‌روزرسانی می‌شوند؟

  • در ERC20 Votes، هر زمان که انتقال توکن یا واگذاری رای (delegation) انجام شود، قدرت رای ثبت و به‌روزرسانی می‌شود.

  • اما در ERC20 Snapshot، فقط زمانی که تابع _snapshot() به‌صورت صریح فراخوانی شود، داده ها ثبت می‌شوند.

کدام استاندارد را انتخاب کنیم؟

انتخاب میان ERC20 Votes و ERC20 Snapshot بیشتر به این بستگی دارد که آیا در پروژه نیاز به واگذاری حق رای وجود دارد یا خیر.

به‌طور کلی، اگر توکن ERC20 قرار است به دارندگان آن نوعی حق مشارکت یا رای گیری اعطا کند، استفاده از استاندارد ERC20 Votes انتخاب بهتری است. اما اگر صرفاً نیاز به ذخیره سازی سوابق موجودی در زمان های مشخص دارید، ERC20 Snapshot گزینه‌ای ساده‌تر و مناسب‌تر خواهد بود.

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

پکیج صفر تا صد آموزش سئو و بهینه سازی بصورت عملی
  • انتشار: ۲۱ تیر ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید