پاداش گیری در Compound V3 | راهنمای جامع کسب توکن COMP در دیفای

پروتکل Compound در نسخه سوم خود، توکن های COMP را به عنوان پاداش به تامین کنندگان نقدینگی (lenders) و وام گیرندگان (borrowers) اختصاص می دهد. میزان این پاداش ها بر اساس سهم هر کاربر از فعالیت های وام دهی و وام گیری در یک بازار مشخص محاسبه می شود.

الگوریتم تخصیص این پاداش ها شباهت زیادی به الگوریتم معروف MasterChef دارد که در سیستم های استیکینگ (Staking) مورد استفاده قرار می گیرد. بنابراین پیشنهاد می شود پیش از بررسی این بخش، ابتدا با سازوکار الگوریتم MasterChef آشنا شوید تا درک بهتری از فرآیند پاداش دهی در Compound V3 داشته باشید.

نمای کلی از سیستم پاداش دهی در Compound V3

در نسخه سوم Compound، سازوکار پاداش دهی شباهت زیادی به الگوریتم MasterChef دارد. این سیستم بررسی می‌کند که یک واحد فرضی USDC «استیک شده» از ابتدا تا امروز چقدر پاداش دریافت کرده است. منظور از استیکینگ در اینجا می‌تواند وام دهی یا وام گیری باشد. هر دو فعالیت پاداش دریافت می‌کنند.

سیستم Compound برای ردیابی پاداش ها از شاخص های trackingSupplyIndex و trackingBorrowIndex استفاده می‌کند. این شاخص ها میزان پاداش تجمع‌یافته برای یک USDC وام داده شده یا وام گرفته شده را از ابتدا ثبت می‌کنند. این ساختار مشابه شاخص baseSupplyIndex در خود Compound یا rewardPerTokenAcc در MasterChef است.

با افزایش تعداد USDC های استیک شده، میزان پاداش هر واحد USDC کاهش می‌یابد. این اتفاق باعث رقیق شدن (dilution) پاداش ها می‌شود. برعکس، اگر کاربران کمتری مشارکت کنند، پاداش هر واحد بیشتر می‌شود.

برخلاف MasterChef، در Compound V3 نرخ پاداش از ابتدا ثابت است. این نرخ از طریق متغیرهای baseTrackingSupplySpeed و baseTrackingBorrowSpeed تعریف می‌شود. البته حاکمیت پروتکل (Governance) می‌تواند این مقادیر را با عملیات rescale تغییر دهد.

کاربران برای دریافت پاداش، باید از طریق قرارداد جداگانه‌ای به نام Comet Rewards اقدام کنند. این قرارداد مستقل از قرارداد اصلی وام دهی عمل می‌کند.

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

همچنین پاداش ها به‌صورت خودکار به کیف پول کاربر واریز نمی‌شوند. هر کاربر باید پاداش های خود را از طریق یک تراکنش جداگانه مطالبه (claim) کند. تصویر زیر، رابط کاربری صفحه دریافت توکن‌های COMP را نشان می‌دهد. دایره زرد رنگ، دکمه دریافت پاداش را مشخص کرده است.

claim COMP UI

قرارداد Comet Rewards نیاز به تأمین دوره ای منابع دارد

قرارداد Comet Rewards توانایی تولید (mint) توکن های COMP را ندارد و به انتقال این توکن ها از سمت نهاد حاکمیتی (Governance) وابسته است. عرضه کل توکن های COMP برابر با ۱۰ میلیون عدد است و همه آن ها قبلا تولید شده اند. بخش قابل توجهی از این توکن ها در خزانه حاکمیتی نگهداری می‌شود.

به صورت دوره‌ای، مقدار مشخصی از توکن های COMP از خزانه Governance به قرارداد پاداش منتقل می‌شود تا منابع آن دوباره شارژ شود. برای نمونه می‌توانید دو تراکنش زیر را مشاهده کنید که با هدف شارژ قرارداد پاداش انجام شده اند:

آدرس قرارداد پاداش در شبکه اصلی اتریوم به صورت زیر است:
0x1B0e765F6224C21223AeA2af16c1C46E38885a40

از آنجا که عرضه توکن های COMP محدود و از پیش تولید شده است، سیستم پاداش‌دهی به مشارکت‌کنندگان در اکوسیستم نمی‌تواند به طور نامحدود ادامه داشته باشد؛ مگر اینکه Governance اقدام به خرید توکن های COMP از بازار آزاد کند.

در تصویر زیر از Etherscan، بیشتر تراکنش های ثبت‌شده مربوط به عملیات دریافت (claim) توکن های COMP هستند (مشخص‌شده با کادر آبی). همچنین مشاهده می‌کنید که این قرارداد در حال حاضر حدود ۷۳٬۰۰۰ توکن COMP را در اختیار دارد (نشان داده شده با فلش آبی).

قرارداد پاداش compound در Etherscan

رفتار trackingSupplyIndex و trackingBorrowIndex مشابه rewardPerTokenAcc است

نمودار زیر مفهومی آشنا از الگوریتم MasterChef را نشان می‌دهد. هر چه مقدار بیشتری از USDC «استیک» شود، پاداش تعلق‌گرفته به هر واحد از آن کمتر خواهد بود. دلیل این موضوع، صدور مقدار ثابتی از پاداش در هر بازه زمانی است که با استفاده از دو شاخص trackingSupplyIndex و trackingBorrowIndex تعیین می‌شود.

یک تفاوت مهم در این نسخه وجود دارد: اگر مقدار USDC وام داده شده یا وام گرفته شده (خط صورتی) کمتر از آستانه baseMinForRewards (نشان داده شده با متن قرمز و خط‌چین) باشد، هیچ پاداشی به آن تخصیص نمی‌یابد. در نتیجه، شاخص trackingSupplyIndex یا trackingBorrowIndex در آن بروزرسانی خاص افزایش پیدا نمی‌کند.

رشد trackingSupplyIndex

این شاخص ها به‌صورت مستقیم در دسترس عموم قرار ندارند. با این حال می‌توان آن‌ها را از طریق تابع عمومی totalsBasic() در قرارداد CometExt استخراج کرد. از آنجا که CometExt یک قرارداد مجزا محسوب می‌شود و فراخوانی‌های آن به‌صورت delegatecall از سمت قرارداد اصلی Comet انجام می‌گیرد، نمی‌توان این مقادیر را مستقیماً از طریق Etherscan مشاهده کرد. برای دستیابی به این اطلاعات، ابزار cast از مجموعه Foundry استفاده می‌شود. تصویر زیر نمونه‌ای از خروجی این پرس‌وجو را در محیط Foundry نشان می‌دهد:

کوئری totalsBasic() در foundry cast

متغیر baseMinForRewards

متغیر baseMinForRewards در خط 86 از فایل Comet.sol تعریف شده است.

متغییر baseMinForRewards

در پروتکل Compound، زمانی که مجموع USDC وام داده شده کمتر از یک میلیون دلار باشد (یعنی کمتر از 1e12 واحد USDC با توجه به اینکه USDC دارای ۶ رقم اعشار است)، هیچ پاداشی به تامین کنندگان تعلق نمی‌گیرد. به همین صورت، اگر مجموع USDC وام گرفته شده کمتر از یک میلیون دلار باشد، وام گیرندگان نیز پاداش COMP دریافت نخواهند کرد.

هدف از این آستانه، جلوگیری از تخصیص پاداش در شرایطی است که مشارکت در بازار به‌اندازه کافی بالا نیست.

در تصویر زیر، نمونه‌ای از استعلام مقدار baseMinForRewards در Etherscan را مشاهده می‌کنید:

کوئری baseMinForRewards در Etherscan

متغیر baseMinForRewards برای جلوگیری از سرریز شدن شاخص تجمع پاداش طراحی شده است

همانطور که می‌دانید، پاداش تجمع‌یافته به ازای هر توکن، به‌طور معکوس با تعداد توکن های استیک شده نسبت دارد. بنابراین اگر مجموع توکن های استیک شده بسیار کم باشد، شاخص تجمع پاداش (accumulator) با سرعت زیادی رشد می‌کند و ممکن است در مدت کوتاهی با سرریز (overflow) مواجه شود.

این موضوع برای حسابرسان امنیتی یک نکته مهم اما اغلب نادیده‌گرفته‌شده به‌شمار می‌رود، زیرا بسیاری از تست های رایج به‌راحتی نمی‌توانند سرریز در accumulator را تشخیص دهند. برای جلوگیری از این آسیب پذیری، باید اطمینان حاصل کرد که شاخص تجمع پاداش در طول چند سال دچار سرریز نخواهد شد.

این مسئله دو راه‌حل دارد: یا باید نرخ پاداش (reward rate) بسیار پایین در نظر گرفته شود، یا مقدار استیک‌شده به اندازه کافی بالا باشد تا نرخ رشد accumulator کنترل شود.

بازنگری در تابع accrueInternal()

هر زمان که تابع accrueInternal() فراخوانی شود، شاخص‌های trackingSupplyIndex و trackingBorrowIndex به‌روزرسانی می‌شوند.

کدی که در ادامه آمده است، منطق توضیح‌داده‌شده در بخش‌های قبلی را پیاده سازی می‌کند. شروط if که با کادر قرمز مشخص شده‌اند، از افزایش پاداش‌ شاخص های trackingSupplyIndex و trackingBorrowIndex جلوگیری می‌کنند، در صورتی که مقدار وام دهی یا وام گیری کمتر از baseMinForRewards باشد.

در همین حال، متغیرهای baseTrackingSupplySpeed و baseTrackingBorrowSpeed (که با کادر آبی مشخص شده‌اند) از نوع تغییرناپذیر (immutable) هستند. بنابراین، میزان افزایشی که به شاخص‌ها اعمال می‌شود تنها به دو عامل بستگی دارد:
۱. مدت زمان سپری شده (timeElapsed)
۲. و به‌طور معکوس، مقدار کل وام داده شده (totalSupplyBase) یا وام گرفته شده (totalBorrowBase)

تابع accrueInternal

می‌توان به baseTrackingSupplySpeed و baseTrackingBorrowSpeed به‌عنوان «میزان پاداش در واحد زمان» نگاه کرد. زمانی که این مقدار در timeElapsed ضرب شود، مجموع پاداش انباشته‌شده برای یک واحد USDC مشارکت‌کننده محاسبه می‌شود. در مرحله پایانی، تقسیم این مقدار بر totalSupplyBase یا totalBorrowBase باعث می‌شود پاداش به‌طور متناسب با مقدار کلی، بین تمام شرکت‌کنندگان تقسیم (یا به اصطلاح رقیق) شود.

متغیرهای baseTrackingSupplySpeed و baseTrackingBorrowSpeed

این دو متغیر نقش مشابهی با rewardPerBlock در الگوریتم MasterChef دارند. آن‌ها مشخص می‌کنند که شاخص‌های تجمع پاداش (accumulators) با چه سرعتی افزایش پیدا می‌کنند؛ یعنی مستقیماً تعیین‌کننده نرخ رشد trackingSupplyIndex و trackingBorrowIndex هستند.

می‌توان مقدار این متغیرها را از طریق پراکسی قرارداد Comet در Etherscan مشاهده کرد.

متغیرهای baseTrackingSupplySpeed و baseTrackingBorrowSpeed در Etherscan

در Etherscan نیز دیده می‌شود که هر دوی این متغیرها از مقیاسی به نام trackingIndexScale برای تعیین اعشار استفاده می‌کنند. طبق اطلاعات موجود، مقدار trackingIndexScale برابر با 1e15 است.

trackingIndexScale

بنابراین این متغیرها از نوع عدد اعشاری با دقت ثابت ۱۵ رقم اعشار (15-decimal fixed point) هستند. مقادیر واقعی آن‌ها به صورت زیر است:

  • baseTrackingSupplySpeed = 0.002979166666666

  • baseTrackingBorrowSpeed = 0.004414467592592

در فایل Comet.sol، این متغیرها همراه با توضیحات مربوط به مقیاس آن‌ها تعریف شده‌اند. تصویر زیر از همین بخش کد تهیه شده است و نحوه تعریف این متغیرها را نشان می‌دهد:

تعاریف متغیرهای مربوط به شاخص های ردیابی پاداش

ردیابی پاداش در سطح کاربر: متغیرهای baseTrackingAccrued و baseTrackingIndex

در سیستم پاداش دهی Compound، مانند الگوریتم MasterChef، پاداش ها زمانی برای یک حساب کاربری انباشته می‌شوند که آن حساب یک تراکنش تغییر وضعیت (state-changing transaction) انجام دهد. به بیان دیگر، تنها زمانی که کاربر عملی مثل وام دادن، برداشت، وام گرفتن یا بازپرداخت انجام دهد، پاداش های مربوطه به حساب او اضافه می‌شود.

همچنین درست مانند MasterChef، مقدار پاداشی که کاربر دریافت می‌کند، به دو عامل بستگی دارد:

  1. موجودی حساب کاربر (یعنی چه مقدار USDC وام داده یا وام گرفته شده دارد)

  2. میزان تغییر شاخص پاداش (index یا همان accumulator) از آخرین زمانی که کاربر یک عملیات تغییر وضعیت انجام داده است

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

ساختار UserBasic

متغیر baseTrackingIndex چگونه کار می‌کند

متغیر baseTrackingIndex در ساختار UserBasic نشان‌دهنده آخرین مقدار شاخص جهانی پاداش (trackingSupplyIndex یا trackingBorrowIndex) در زمانی است که اطلاعات حساب کاربر برای آخرین بار به‌روزرسانی شده است. نوع شاخصی که ذخیره می‌شود، بستگی به نقش کاربر دارد:

  • اگر کاربر وام دهنده (lender) باشد، مقدار trackingSupplyIndex در baseTrackingIndex ذخیره می‌شود.

  • اگر کاربر وام گیرنده (borrower) باشد، مقدار trackingBorrowIndex در آن قرار می‌گیرد.

وقتی کاربر تراکنش جدیدی انجام می‌دهد، سیستم اختلاف بین شاخص فعلی (trackingSupplyIndex یا trackingBorrowIndex) و مقدار ذخیره‌شده در baseTrackingIndex کاربر را محاسبه می‌کند. این اختلاف (Delta) مشخص می‌کند که کاربر در آن تراکنش چه میزان پاداش دریافت خواهد کرد. نمودار زیر را در نظر بگیرید:

trackingSupplyIndex و baseTrackingIndex برای کاربر

هر بار که یک کاربر عملی انجام دهد که باعث تغییر در سرمایه اصلی او (principal) شود—مانند واریز، برداشت، دریافت وام یا بازپرداخت—تابع داخلی updateBasePrincipal() فراخوانی می‌شود. این تابع وظیفه دارد بررسی کند که از آخرین بار که اطلاعات حساب کاربر به‌روزرسانی شده، شاخص پاداش (trackingSupplyIndex یا trackingBorrowIndex) چقدر تغییر کرده است. سپس با توجه به این اختلاف، مقدار پاداش متناسب را محاسبه کرده و به متغیر baseTrackingAccrued کاربر اضافه می‌کند. تابع در زیر نشان داده شده است:

تابع updateBasePrincipal()

جمع‌بندی: نقش baseTrackingIndex و baseTrackingAccrued

به طور خلاصه، متغیر baseTrackingIndex نشان می‌دهد که شاخص جهانی پاداش (index) در آخرین باری که اطلاعات کاربر به‌روزرسانی شده، چه مقداری داشته است. این مقدار مبنای محاسبه اختلاف بعدی برای تخصیص پاداش جدید خواهد بود.

از سوی دیگر، baseTrackingAccrued نشان‌دهنده مجموع پاداش هایی است که از زمان مشارکت کاربر در پروتکل تا به امروز برای او انباشته شده‌اند. این مقدار شامل تمام پاداش های تعلق‌گرفته است، حتی اگر بخشی از آن‌ها قبلاً توسط کاربر مطالبه (claim) شده باشند.

در واقع، قرارداد جداگانه‌ای که مسئول پاداش‌ها است، متغیری به نام reward debt را برای هر کاربر نگه‌داری می‌کند. این متغیر تضمین می‌کند که پاداش‌های قبلاً دریافت‌شده از مقدار کل انباشته‌شده کم شوند، تا در هر لحظه فقط مانده واقعی پاداش‌ها برای کاربر قابل دریافت باشد.

accrualDescaleFactor چیست؟

در کدی که بالاتر بررسی شد، مشاهده می‌کنیم که پاداش انباشته‌شده کاربر پیش از استفاده، بر متغیری به نام accrualDescaleFactor تقسیم می‌شود.

هدف از این تقسیم‌بندی، فراهم‌کردن امکان ردیابی ETH و USDC روی یک مقیاس واحد است. از آنجایی که ETH دارای ۱۸ رقم اعشار و USDC دارای ۶ رقم اعشار است، باید مکانیزمی وجود داشته باشد که هر دو دارایی بتوانند به‌درستی و به‌صورت هماهنگ در متغیر baseTrackingAccrued ذخیره شوند.

برای رسیدن به این هماهنگی، مقدار baseTrackingAccrued مربوط به ETH بر عدد 1e12 تقسیم می‌شود. این کار باعث می‌شود که ETH هم عملاً با دقت ۶ رقم اعشار (هم‌سطح با USDC) ردیابی شود.

در نتیجه، متغیر baseTrackingAccrued قادر است هر دو دارایی ETH و USDC را با یک ساختار واحد مدیریت کند، بدون آنکه نیاز به تبدیل‌های پیچیده یا چندگانه وجود داشته باشد.

دریافت پاداش ها (Claiming Rewards)

برای دریافت پاداش ها، کاربر تنها کافی است تابع claim() را در قرارداد CometReward.sol فراخوانی کند. این تابع، پاداش های انباشته‌شده‌ای را که کاربر تا آن لحظه واجد شرایط دریافت آن‌ها بوده، محاسبه کرده و به کیف پول او منتقل می‌کند. در این فرآیند، از ساختاری به نام rewardsClaimed (مشخص‌شده با کادر قرمز) استفاده می‌شود. این متغیر عملکردی مشابه rewardDebt در الگوریتم MasterChef دارد. یعنی مقدار پاداشی که تاکنون برای کاربر پرداخت شده را نگه می‌دارد تا از محاسبه دوباره یا پرداخت اضافه جلوگیری شود.

تابع دریافت پاداش

پارامتر shouldAccrue چه کاربردی دارد؟

پارامتر shouldAccrue (مشخص‌شده با کادر سبز) مشخص می‌کند که آیا قبل از محاسبه و پرداخت پاداش، باید حساب کاربر به‌روزرسانی شود یا خیر.

اگر کاربر تنها برای دریافت پاداش (claim rewards) یک تراکنش مجزا ارسال کند و هیچ عملیات دیگری در همان تراکنش انجام ندهد، مقدار shouldAccrue باید true باشد. در این حالت، سیستم ابتدا از طریق تابع accrueAccount() شاخص‌های مربوط به پاداش کاربر را به‌روزرسانی می‌کند، سپس محاسبه و پرداخت انجام می‌شود.

اما اگر تابع claim() بعد از یک یا چند تابع دیگر که وضعیت حساب را تغییر می‌دهند (مثل واریز، برداشت، وام گرفتن یا بازپرداخت) فراخوانی شود، نیازی به محاسبه دوباره وجود ندارد. چون آن توابع به‌طور خودکار accrueAccount() را اجرا کرده‌اند. در این شرایط، مقدار shouldAccrue می‌تواند false باشد تا از اجرای تکراری و بی‌مورد جلوگیری شود و هزینه گس (Gas) کاهش یابد.

تابع getRewardAccrued() در CometRewards.sol

در بخش مشخص‌شده با کادر آبی، تابع getRewardAccrued() وظیفه دارد مقدار پاداش قابل پرداخت به کاربر را محاسبه کند.

این تابع ابتدا مقدار baseTrackingAccrued را از ساختار کاربر (UserBasic) در قرارداد Comet دریافت می‌کند. این مقدار نشان‌دهنده کل پاداش انباشته‌شده برای آن کاربر از زمان شروع مشارکت در پروتکل است.

سپس، قرارداد CometRewards مقدار rewardsClaimed (یا همان بدهی پاداش‌ها که قبلاً پرداخت شده‌اند) را از آن کم می‌کند. در نهایت، اختلاف این دو مقدار به عنوان پاداش واقعی که باید در این لحظه به کاربر پرداخت شود، در نظر گرفته می‌شود.

تابع getRewardsAccrued()

ویژگی خاص توکن COMP و تفاوت آن با استاندارد ERC-20

توکن COMP که توسط قرارداد پاداش ها توزیع می‌شود، برخلاف اکثر توکن های ERC-20 معمول، موجودی حساب ها را با نوع داده uint256 ذخیره نمی‌کند. به جای آن، از نوع داده محدودتر uint96 برای نگهداری مقادیر استفاده می‌کند.

نگاشت موجودی توکن‌های COMP

اگر در کدی یا تراکنشی تلاش کنید مقداری بیشتر از این سقف را منتقل (transfer) یا تایید (approve) کنید، تابع مورد نظر با خطا مواجه می‌شود و تراکنش revert خواهد شد.

تابع انتقال COMP وقتی مقدار بیشتر از ۹۶ بیت شود، به حالت اولیه برمی‌گردد.

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

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

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

پکیج آموزش سی شارپ | مختص ورود به بازار کار + آموزش ساخت بازی Quiz of King
  • انتشار: ۳۰ تیر ۱۴۰۴

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

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

مشاهده همه

نظرات

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