پروتکل 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 را نشان میدهد. دایره زرد رنگ، دکمه دریافت پاداش را مشخص کرده است.
قرارداد Comet Rewards نیاز به تأمین دوره ای منابع دارد
قرارداد Comet Rewards توانایی تولید (mint) توکن های COMP را ندارد و به انتقال این توکن ها از سمت نهاد حاکمیتی (Governance) وابسته است. عرضه کل توکن های COMP برابر با ۱۰ میلیون عدد است و همه آن ها قبلا تولید شده اند. بخش قابل توجهی از این توکن ها در خزانه حاکمیتی نگهداری میشود.
به صورت دورهای، مقدار مشخصی از توکن های COMP از خزانه Governance به قرارداد پاداش منتقل میشود تا منابع آن دوباره شارژ شود. برای نمونه میتوانید دو تراکنش زیر را مشاهده کنید که با هدف شارژ قرارداد پاداش انجام شده اند:
آدرس قرارداد پاداش در شبکه اصلی اتریوم به صورت زیر است:
0x1B0e765F6224C21223AeA2af16c1C46E38885a40
از آنجا که عرضه توکن های COMP محدود و از پیش تولید شده است، سیستم پاداشدهی به مشارکتکنندگان در اکوسیستم نمیتواند به طور نامحدود ادامه داشته باشد؛ مگر اینکه Governance اقدام به خرید توکن های COMP از بازار آزاد کند.
در تصویر زیر از Etherscan، بیشتر تراکنش های ثبتشده مربوط به عملیات دریافت (claim) توکن های COMP هستند (مشخصشده با کادر آبی). همچنین مشاهده میکنید که این قرارداد در حال حاضر حدود ۷۳٬۰۰۰ توکن COMP را در اختیار دارد (نشان داده شده با فلش آبی).
رفتار trackingSupplyIndex و trackingBorrowIndex مشابه rewardPerTokenAcc است
نمودار زیر مفهومی آشنا از الگوریتم MasterChef را نشان میدهد. هر چه مقدار بیشتری از USDC «استیک» شود، پاداش تعلقگرفته به هر واحد از آن کمتر خواهد بود. دلیل این موضوع، صدور مقدار ثابتی از پاداش در هر بازه زمانی است که با استفاده از دو شاخص trackingSupplyIndex
و trackingBorrowIndex
تعیین میشود.
یک تفاوت مهم در این نسخه وجود دارد: اگر مقدار USDC وام داده شده یا وام گرفته شده (خط صورتی) کمتر از آستانه baseMinForRewards
(نشان داده شده با متن قرمز و خطچین) باشد، هیچ پاداشی به آن تخصیص نمییابد. در نتیجه، شاخص trackingSupplyIndex
یا trackingBorrowIndex
در آن بروزرسانی خاص افزایش پیدا نمیکند.
این شاخص ها بهصورت مستقیم در دسترس عموم قرار ندارند. با این حال میتوان آنها را از طریق تابع عمومی totalsBasic()
در قرارداد CometExt استخراج کرد. از آنجا که CometExt یک قرارداد مجزا محسوب میشود و فراخوانیهای آن بهصورت delegatecall
از سمت قرارداد اصلی Comet انجام میگیرد، نمیتوان این مقادیر را مستقیماً از طریق Etherscan مشاهده کرد. برای دستیابی به این اطلاعات، ابزار cast
از مجموعه Foundry استفاده میشود. تصویر زیر نمونهای از خروجی این پرسوجو را در محیط Foundry نشان میدهد:
متغیر baseMinForRewards
متغیر baseMinForRewards
در خط 86 از فایل Comet.sol تعریف شده است.
در پروتکل Compound، زمانی که مجموع USDC وام داده شده کمتر از یک میلیون دلار باشد (یعنی کمتر از 1e12
واحد USDC با توجه به اینکه USDC دارای ۶ رقم اعشار است)، هیچ پاداشی به تامین کنندگان تعلق نمیگیرد. به همین صورت، اگر مجموع USDC وام گرفته شده کمتر از یک میلیون دلار باشد، وام گیرندگان نیز پاداش COMP دریافت نخواهند کرد.
هدف از این آستانه، جلوگیری از تخصیص پاداش در شرایطی است که مشارکت در بازار بهاندازه کافی بالا نیست.
در تصویر زیر، نمونهای از استعلام مقدار baseMinForRewards
در Etherscan را مشاهده میکنید:
متغیر baseMinForRewards برای جلوگیری از سرریز شدن شاخص تجمع پاداش طراحی شده است
همانطور که میدانید، پاداش تجمعیافته به ازای هر توکن، بهطور معکوس با تعداد توکن های استیک شده نسبت دارد. بنابراین اگر مجموع توکن های استیک شده بسیار کم باشد، شاخص تجمع پاداش (accumulator) با سرعت زیادی رشد میکند و ممکن است در مدت کوتاهی با سرریز (overflow) مواجه شود.
این موضوع برای حسابرسان امنیتی یک نکته مهم اما اغلب نادیدهگرفتهشده بهشمار میرود، زیرا بسیاری از تست های رایج بهراحتی نمیتوانند سرریز در accumulator را تشخیص دهند. برای جلوگیری از این آسیب پذیری، باید اطمینان حاصل کرد که شاخص تجمع پاداش در طول چند سال دچار سرریز نخواهد شد.
این مسئله دو راهحل دارد: یا باید نرخ پاداش (reward rate) بسیار پایین در نظر گرفته شود، یا مقدار استیکشده به اندازه کافی بالا باشد تا نرخ رشد accumulator کنترل شود.
بازنگری در تابع accrueInternal()
هر زمان که تابع accrueInternal()
فراخوانی شود، شاخصهای trackingSupplyIndex
و trackingBorrowIndex
بهروزرسانی میشوند.
کدی که در ادامه آمده است، منطق توضیحدادهشده در بخشهای قبلی را پیاده سازی میکند. شروط if
که با کادر قرمز مشخص شدهاند، از افزایش پاداش شاخص های trackingSupplyIndex
و trackingBorrowIndex
جلوگیری میکنند، در صورتی که مقدار وام دهی یا وام گیری کمتر از baseMinForRewards
باشد.
در همین حال، متغیرهای baseTrackingSupplySpeed
و baseTrackingBorrowSpeed
(که با کادر آبی مشخص شدهاند) از نوع تغییرناپذیر (immutable) هستند. بنابراین، میزان افزایشی که به شاخصها اعمال میشود تنها به دو عامل بستگی دارد:
۱. مدت زمان سپری شده (timeElapsed
)
۲. و بهطور معکوس، مقدار کل وام داده شده (totalSupplyBase
) یا وام گرفته شده (totalBorrowBase
)
میتوان به baseTrackingSupplySpeed
و baseTrackingBorrowSpeed
بهعنوان «میزان پاداش در واحد زمان» نگاه کرد. زمانی که این مقدار در timeElapsed
ضرب شود، مجموع پاداش انباشتهشده برای یک واحد USDC مشارکتکننده محاسبه میشود. در مرحله پایانی، تقسیم این مقدار بر totalSupplyBase
یا totalBorrowBase
باعث میشود پاداش بهطور متناسب با مقدار کلی، بین تمام شرکتکنندگان تقسیم (یا به اصطلاح رقیق) شود.
متغیرهای baseTrackingSupplySpeed و baseTrackingBorrowSpeed
این دو متغیر نقش مشابهی با rewardPerBlock
در الگوریتم MasterChef دارند. آنها مشخص میکنند که شاخصهای تجمع پاداش (accumulators) با چه سرعتی افزایش پیدا میکنند؛ یعنی مستقیماً تعیینکننده نرخ رشد trackingSupplyIndex
و trackingBorrowIndex
هستند.
میتوان مقدار این متغیرها را از طریق پراکسی قرارداد Comet در Etherscan مشاهده کرد.
در Etherscan نیز دیده میشود که هر دوی این متغیرها از مقیاسی به نام trackingIndexScale
برای تعیین اعشار استفاده میکنند. طبق اطلاعات موجود، مقدار trackingIndexScale
برابر با 1e15
است.
بنابراین این متغیرها از نوع عدد اعشاری با دقت ثابت ۱۵ رقم اعشار (15-decimal fixed point) هستند. مقادیر واقعی آنها به صورت زیر است:
-
baseTrackingSupplySpeed = 0.002979166666666
-
baseTrackingBorrowSpeed = 0.004414467592592
در فایل Comet.sol
، این متغیرها همراه با توضیحات مربوط به مقیاس آنها تعریف شدهاند. تصویر زیر از همین بخش کد تهیه شده است و نحوه تعریف این متغیرها را نشان میدهد:
ردیابی پاداش در سطح کاربر: متغیرهای baseTrackingAccrued و baseTrackingIndex
در سیستم پاداش دهی Compound، مانند الگوریتم MasterChef، پاداش ها زمانی برای یک حساب کاربری انباشته میشوند که آن حساب یک تراکنش تغییر وضعیت (state-changing transaction) انجام دهد. به بیان دیگر، تنها زمانی که کاربر عملی مثل وام دادن، برداشت، وام گرفتن یا بازپرداخت انجام دهد، پاداش های مربوطه به حساب او اضافه میشود.
همچنین درست مانند MasterChef، مقدار پاداشی که کاربر دریافت میکند، به دو عامل بستگی دارد:
-
موجودی حساب کاربر (یعنی چه مقدار USDC وام داده یا وام گرفته شده دارد)
-
میزان تغییر شاخص پاداش (
index
یا همانaccumulator
) از آخرین زمانی که کاربر یک عملیات تغییر وضعیت انجام داده است
در نتیجه، اگر یک کاربر برای مدتی هیچ عملیاتی انجام ندهد، شاخص پاداش به رشد خود ادامه میدهد اما پاداش او فقط در لحظهای که دوباره تراکنشی انجام دهد، محاسبه و به حسابش اضافه میشود.
متغیر baseTrackingIndex چگونه کار میکند
متغیر baseTrackingIndex
در ساختار UserBasic
نشاندهنده آخرین مقدار شاخص جهانی پاداش (trackingSupplyIndex
یا trackingBorrowIndex
) در زمانی است که اطلاعات حساب کاربر برای آخرین بار بهروزرسانی شده است. نوع شاخصی که ذخیره میشود، بستگی به نقش کاربر دارد:
-
اگر کاربر وام دهنده (lender) باشد، مقدار
trackingSupplyIndex
درbaseTrackingIndex
ذخیره میشود. -
اگر کاربر وام گیرنده (borrower) باشد، مقدار
trackingBorrowIndex
در آن قرار میگیرد.
وقتی کاربر تراکنش جدیدی انجام میدهد، سیستم اختلاف بین شاخص فعلی (trackingSupplyIndex
یا trackingBorrowIndex
) و مقدار ذخیرهشده در baseTrackingIndex
کاربر را محاسبه میکند. این اختلاف (Delta) مشخص میکند که کاربر در آن تراکنش چه میزان پاداش دریافت خواهد کرد. نمودار زیر را در نظر بگیرید:
هر بار که یک کاربر عملی انجام دهد که باعث تغییر در سرمایه اصلی او (principal) شود—مانند واریز، برداشت، دریافت وام یا بازپرداخت—تابع داخلی updateBasePrincipal()
فراخوانی میشود. این تابع وظیفه دارد بررسی کند که از آخرین بار که اطلاعات حساب کاربر بهروزرسانی شده، شاخص پاداش (trackingSupplyIndex
یا trackingBorrowIndex
) چقدر تغییر کرده است. سپس با توجه به این اختلاف، مقدار پاداش متناسب را محاسبه کرده و به متغیر baseTrackingAccrued
کاربر اضافه میکند. تابع در زیر نشان داده شده است:
جمعبندی: نقش 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
(یا همان بدهی پاداشها که قبلاً پرداخت شدهاند) را از آن کم میکند. در نهایت، اختلاف این دو مقدار به عنوان پاداش واقعی که باید در این لحظه به کاربر پرداخت شود، در نظر گرفته میشود.
ویژگی خاص توکن COMP و تفاوت آن با استاندارد ERC-20
توکن COMP که توسط قرارداد پاداش ها توزیع میشود، برخلاف اکثر توکن های ERC-20 معمول، موجودی حساب ها را با نوع داده uint256
ذخیره نمیکند. به جای آن، از نوع داده محدودتر uint96
برای نگهداری مقادیر استفاده میکند.
اگر در کدی یا تراکنشی تلاش کنید مقداری بیشتر از این سقف را منتقل (transfer) یا تایید (approve) کنید، تابع مورد نظر با خطا مواجه میشود و تراکنش revert خواهد شد.
در پایان باید گفت، درک سازوکار پاداش گیری در Compound V3 نیازمند آشنایی با مفاهیم پایهای قراردادهای هوشمند و ساختارهای عددی دقیق است. اگر هنوز در ابتدای مسیر یادگیری هستید و نمیدانید از کجا و با چه زبانی شروع کنید، پیشنهاد میکنیم حتماً صفحه آموزش برنامه نویسی را ببینید. در این راهنما، مسیر یادگیری از صفر توضیح داده شده و به شما کمک میکند بهترین نقطه شروع را بر اساس هدف و علاقه خود انتخاب کنید.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۳۰ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس