الگوریتم استیکینگ MasterChef و Synthetix از یک منطق مشخص برای توزیع منصفانه پاداش میان کاربران استیککننده استفاده میکند. در این الگوریتمها، قرارداد بر اساس میزان مشارکت هر کاربر و مدت نگهداری توکنها، پاداش اختصاص میدهد. برای کسانی که به دنبال درک بهتر مفاهیم بلاکچین یا آموزش برنامه نویسی قراردادهای هوشمند هستند، شناخت این مدلها اهمیت زیادی دارد. هر دو سیستم برای کاهش مصرف گس، از یک شمارنده تجمیعی در سطح توکن بهره میبرند و پاداشها را با تأخیر، در زمان مناسب به کاربران تخصیص میدهند.
فرض کنید یک مخزن پاداش با مقدار ثابت ۱۰۰٬۰۰۰ توکن REWARD داریم و میخواهیم آن را از بلوک ۱ تا بلوک ۱۰۰ بهصورت منصفانه میان کاربران استیک کننده توزیع کنیم.
هدف ما این است که در هر بلوک، ۱٬۰۰۰ توکن REWARD به نسبت سهم هر کاربر در استخر، بین آنها تقسیم شود.
برای مثال، اگر در یک بلوک مشخص، موجودی استیک شده در قرارداد به صورت زیر باشد:
مقدار استیکشده | درصد از کل استخر |
---|---|
Alice: 100 | 25٪ |
Bob: 100 | 25٪ |
Chad: 200 | 50٪ |
در این صورت، ۱٬۰۰۰ توکن REWARD در آن بلوک به شکل زیر توزیع خواهد شد:
کاربر | مقدار استیکشده | درصد از استخر | پاداش دریافتی |
---|---|---|---|
Alice | 100 | 25٪ | 250 |
Bob | 100 | 25٪ | 250 |
Chad | 200 | 50٪ | 500 |
این مکانیزم باعث میشود که پاداشها به شکلی دقیق و منصفانه، متناسب با مشارکت کاربران در طول زمان، تخصیص یابند؛ بدون آنکه نیاز به محاسبات سنگین و پرهزینه در هر تراکنش باشد.
اصطلاح شناسی توکن و پاداش
توکنی که کاربران استیک میکنند و ارزی که به عنوان پاداش دریافت میشود، ممکن است یکسان یا متفاوت باشند. برای شفافیت در توضیحات، ما این دو را با نامهای token و reward مشخص میکنیم — گاهی نیز از حروف بزرگ TOKEN و REWARD استفاده خواهیم کرد.
ارسال تراکنش در هر بلوک برای توزیع پاداش، راهکار مناسبی نیست
در نگاه اول، ممکن است راه حل ساده این باشد که یک ربات خارج از زنجیره (offchain bot) در هر بلوک، یک تراکنش ارسال کند. این ربات باید موجودیهای TOKEN هر استیککننده را از قرارداد خوانده و متناسب با درصد سهم آنها از استخر، برایشان توکن REWARD تولید (mint) کند.
اما این روش دو مشکل بزرگ دارد:
-
عدم قطعیت در ثبت تراکنش در هر بلوک: هیچ راه تضمینشدهای وجود ندارد که تراکنش های ربات در همه بلوک ها ثبت شوند. اگر حتی یک بلوک از قلم بیفتد، کاربران پاداش کمتری از آنچه باید دریافت میکردند خواهند گرفت.
-
هزینه زیاد کارمزد: ارسال مداوم تراکنش در هر بلوک باعث میشود که هزینه گس بسیار زیادی تولید شود.
نیازی به ارسال تراکنش در هر بلوک نیست؛ میتوان پاداشهای معوقه را یکجا پرداخت کرد
فرض کنید متغیری به نام lastUpdateBlockNumber
داشته باشیم که آخرین بلوکی را که در آن پاداشی صادر شده، ثبت کند. برای محاسبه تعداد بلوکهایی که از آن زمان گذشته، کافیست از رابطه block.number - lastUpdateBlockNumber
استفاده کنیم.
بر این اساس، میتوان از صدور پاداش در برخی از بلوکها صرفنظر کرد و بعداً هنگام اجرای یک تراکنش، مجموع پاداشهای معوقه را به صورت یکجا محاسبه و تخصیص داد. این روش باعث صرفهجویی در گس و افزایش بهرهوری میشود، بدون آنکه به عدالت در توزیع پاداشها آسیبی وارد شود.
نموداری که در ادامه آمده، این فرآیند را بهصورت تصویری نمایش میدهد.
حتی اگر توکنهای REWARD را تولید (mint) کنیم، باز هم یک چالش باقی میماند: چگونه این پاداشها را بهدرستی بین کاربران استیککننده، متناسب با درصد سهمشان از استخر، توزیع کنیم؟
علاوه بر این، ما نمیتوانیم با اطمینان بگوییم که موجودیهای استیکشده در بازه بین آخرین توزیع پاداش تا لحظه فعلی ثابت باقی ماندهاند. بهعنوان نمونه، تصور کنید Chad از زمان دقیق سنجش موجودیها در بلوک ۱۰۰ خبر داشته و در بلوک ۹۹ یک سپرده بزرگ واریز کرده تا سهم بیشتری از پاداش دریافت کند.
اما خوشبختانه حل این مشکل پیچیده نیست.
اصل کلیدی: تا وقتی تراکنشی انجام نشود، موجودی تغییر نمیکند
بهجای اینکه یک ربات، هر ۲۰ بلوک یک بار تراکنش ارسال کند، میتوان منتظر ماند تا خود کاربران از طریق توابعی که وضعیت قرارداد را تغییر میدهند، مانند deposit()
یا withdraw()
، با قرارداد تعامل داشته باشند.
تا زمانی که هیچکدام از این توابع توسط کاربران فراخوانی نشوند، میتوان با اطمینان گفت که هیچ کاربری موجودی استیک شده خود را تغییر نمیدهد.
مثلاً اگر Alice و Bob در فاصلهی بلوک ۱۰ تا ۱۵ هر کدام ۵۰٪ از کل توکنهای استیک شده را در اختیار داشته باشند، قرارداد میتواند بهراحتی ۵٬۰۰۰ توکن REWARD (۵ بلوک × ۱٬۰۰۰) صادر کند و به هرکدام ۵۰٪ از آن را اختصاص دهد.
در این سیستم، Chad یا هر فرد دیگری نمیتواند با واریز آنی، پاداش بیشتری دریافت کند؛ چون بهمحض اینکه تابع deposit()
را فراخوانی کنند، تابع توزیع پاداش فعال میشود و این تابع بهگونهای طراحی شده که سپرده اخیر آنها را در محاسبه پاداش لحاظ نمیکند.
نموداری که در ادامه قرار دارد، روند تغییر موجودی کاربران را در طول زمان نمایش میدهد. این تغییرات فقط زمانی اتفاق میافتد که یکی از کاربران واقعاً با قرارداد هوشمند تراکنش انجام دهد.
اما این راهحل در مقیاس بزرگ قابل استفاده نیست.
پیمایش روی تمام استیککنندهها هزینه گس بالایی دارد
اگر در هر بار فراخوانی تابع deposit()
یا withdraw()
بخواهیم پاداش REWARD را برای تمام کاربران توزیع کنیم، هزینهی گس بسیار بالایی خواهد داشت، مخصوصاً وقتی تعداد استیککنندهها زیاد باشد. توزیع توکنهای ERC-20 به تنهایی ارزان نیست، و اگر این عملیات دهها بار در یک حلقه انجام شود، هزینهها بهطور چشمگیری افزایش مییابد.
برای اینکه این سیستم استیکینگ بهصورت بهینه کار کند، پاداشها فقط زمانی منتقل میشوند که کاربر خودش یک تراکنش تغییر وضعیت انجام دهد. در واقع، اگر کاربری برای دریافت پاداش اقدامی نکند، پاداش او بهصورت معوق باقی میماند و در قرارداد ذخیره میشود تا زمانی که او بخواهد آن را مطالبه کند.
این روش باعث میشود که نیاز به انجام چندین تراکنش ERC-20 در هر بار تعامل از بین برود.
برای رسیدن به کارایی بالا، باید دو اصل را رعایت کرد:
-
تنها متغیرهای مربوط به همان حسابی که تراکنش را انجام میدهد بهروزرسانی شوند.
-
فقط یک متغیر سراسری بهروزرسانی شود که رشد کلی پاداش را برای تمام کاربران محاسبه میکند. نباید وضعیت تکتک حسابها بهروزرسانی شود.
راهحل هوشمندانه: پاداش انباشتهشده برای هر توکن
فرض کنید بتوانیم بهطور دقیق محاسبه کنیم که هر یک توکن استیکشده، از ابتدای توزیع پاداش تا اکنون، چه مقدار پاداش جمع کرده است.
در این صورت، پاداشی که هر کاربر باید دریافت کند بهسادگی برابر است با:
مقدار توکن استیکشده توسط کاربر × پاداش انباشتهشده هر توکن
مثلاً اگر بدانیم که هر توکن استیکشده از ابتدای قرارداد تا این لحظه، ۱۲ توکن REWARD تولید کرده و Alice ۱۰۰ توکن استیک کرده، پس او مستحق دریافت ۱٬۲۰۰ توکن REWARD است.
این دقیقاً مثل این است که بگوییم:
«اگر هر دلار سپردهگذاریشده در بانک ما از زمان افتتاح، ۴۰ سنت سود دریافت کرده، کسی که از روز اول ۱۰۰ دلار سپرده گذاشته، تا امروز ۴۰٪ سود گرفته است.»
این مفهوم ما را به دو پرسش مهم میرساند:
-
چگونه پاداش انباشتهشده برای هر توکن را از ابتدای قرارداد تا لحظه فعلی ردیابی کنیم؟
-
اگر Alice از ابتدای کار در استخر نبوده و فقط اخیراً سپردهگذاری کرده باشد، چه کار کنیم؟
چگونه پاداش انباشته شده برای هر توکن را از ابتدای قرارداد ردیابی کنیم؟
از آنجا که در هر بلوک تعداد ثابتی پاداش صادر میشود (مثلاً ۱٬۰۰۰ توکن REWARD در مثال ما)، هرچه تعداد توکنهای استیکشده در قرارداد بیشتر شود، سهم هر توکن از این ۱٬۰۰۰ پاداش کمتر خواهد شد. بنابراین، چیزی که اهمیت دارد تعداد افراد استیککننده نیست، بلکه مجموع توکنهای استیکشده در قرارداد است.
مثال فرضی:
بلوکها | پاداش در هر بلوک | تعداد کل توکنهای استیکشده | پاداش هر توکن در هر بلوک |
---|---|---|---|
بلوکهای ۱ تا ۵ | ۱٬۰۰۰ | ۱۰۰ | ۱۰ |
بلوکهای ۶ تا ۱۳ | ۱٬۰۰۰ | ۲۰۰ | ۵ |
بلوکهای ۱۴ تا ۱۵ | ۱٬۰۰۰ | ۱۰۰ | ۱۰ |
بلوکهای ۱۶ تا ۲۰ | ۱٬۰۰۰ | ۵۰۰ | ۲ |
هرچه توکنهای بیشتری در قرارداد استیک شده باشند، پاداش هر توکن در هر بلوک کمتر خواهد بود.
استیکهای بزرگتر باعث رقیق شدن (dilution) پاداشها میشوند، و در نتیجه، درآمد هر توکن کاهش پیدا میکند.
در نمودار زیر:
-
خط قرمز نشاندهنده مجموع توکن های استیک شده در هر بلوک است.
-
خط بنفش مقدار پاداشی را نشان میدهد که یک توکن در آن بلوک کسب میکند.
-
محور افقی نشاندهنده بلوک هاست که به سمت راست حرکت میکنند.
رابطه معکوس بین این دو متغیر کاملاً مشهود است: هر زمان مقدار توکن های استیک شده بالا میرود، پاداش دریافتی هر توکن کاهش مییابد، و برعکس.
نکته کلیدی در ردیابی پاداش انباشتهشده برای هر توکن
هر زمان که یک تراکنش تغییر وضعیت (state changing transaction) مانند deposit()
یا withdraw()
رخ میدهد، ما به گذشته نگاه میکنیم و موارد زیر را محاسبه میکنیم:
-
تعداد بلوکهایی که از آخرین بهروزرسانی گذشته
-
ضرب آن تعداد بلوکها در مقدار پاداش هر بلوک (مثلاً ۱٬۰۰۰ توکن)
-
تقسیم نتیجه بر مجموع توکنهای استیکشده در آن بازه زمانی
مقدار نهایی، میزان پاداشی است که هر توکن در آن بازه زمانی بهدست آورده است.
سپس مقداری که به دست آوردیم را به یک متغیر سراسری (global accumulator) اضافه میکنیم — این متغیر از ابتدای زمان مقدارش صفر بوده و در هر تراکنش بهروزرسانی میشود.
اگر این فرآیند را در هر بار ورود یک تراکنش جدید تکرار کنیم، در نهایت همیشه میتوانیم بدانیم هر توکن از ابتدای قرارداد تا این لحظه، چه مقدار پاداش جمع کرده است.
در ادامه، همان نمودار قبلی با اضافه شدن خط انباشتگر نمایش داده شده است.
و در اینجا یک جدول داریم که همان مقادیر قبلی را نمایش میدهد:
بلوکها | پاداش در هر بلوک | مجموع توکن های استیک شده | پاداش هر توکن در هر بلوک | تعداد بلوک در بازه | مجموع پاداش در بازه | پاداش انباشته هر توکن |
---|---|---|---|---|---|---|
بلوک ۰ | ۰ | ۰ | ۰ | ۰ | ۰ | ۰ |
بلوک های ۱ تا ۵ | ۱٬۰۰۰ | ۱۰۰ | ۱۰ | ۵ | ۵۰ | ۵۰ |
بلوک های ۶ تا ۱۳ | ۱٬۰۰۰ | ۲۰۰ | ۵ | ۸ | ۴۰ | ۹۰ |
بلوک های ۱۴ تا ۱۵ | ۱٬۰۰۰ | ۱۰۰ | ۱۰ | ۲ | ۲۰ | ۱۱۰ |
بلوک های ۱۶ تا ۲۰ | ۱٬۰۰۰ | ۵۰۰ | ۲ | ۵ | ۱۰ | ۱۲۰ |
به عبارت دیگر، یک توکن که در تمام این بازه استیک شده، تا پایان بلوک ۲۰ در مجموع ۱۲۰ توکن REWARD دریافت کرده است.
تست موردی: محاسبه پاداش Alice که از ابتدای قرارداد استیک کرده
بیایید یک مثال بسیار ساده را بررسی کنیم. در اینجا نیز در هر بلوک، ۱٬۰۰۰ توکن REWARD صادر میشود. در طول ۲۰ بلوک، مجموعاً ۲۰٬۰۰۰ پاداش توزیع خواهد شد.
رفتار Alice و Bob به این شکل است:
-
Alice از بلوک ۱ تا ۲۰ تعداد ۱۰۰ توکن استیک کرده است.
-
Bob در بلوک ۱۰، تعداد ۱۰۰ توکن وارد استخر میکند.
در نتیجه، در بلوک ۲۰، Alice باید ۷۵٪ از کل پاداشی را دریافت کند که قرارداد تا آن لحظه صادر کرده است، یعنی ۱۵٬۰۰۰ توکن REWARD.
نمودار سهم کاربران از استخر پاداش در هر بلوک، به صورت بصری شبیه شکل زیر خواهد بود:
از بلوک ۱ تا بلوک ۱۰، قرارداد در هر بلوک به هر توکن ۱۰ پاداش اختصاص داد (۱٬۰۰۰ ÷ ۱۰۰). در این بازه ۱۰ بلوکی، هر توکن در مجموع ۱۰۰ پاداش دریافت کرد (۱۰ پاداش در هر بلوک × ۱۰ بلوک).
وقتی Bob در بلوک ۱۰ سپردهگذاری کرد، تعداد توکنهای استیکشده دو برابر شد و در نتیجه، پاداش هر توکن در هر بلوک به ۵ کاهش پیدا کرد (۱٬۰۰۰ ÷ ۲۰۰). در بازه بعدی، یعنی از بلوک ۱۱ تا ۲۰، هر توکن ۵۰ پاداش دیگر به دست آورد.
در مجموع، هر توکن از بلوک ۱ تا ۱۰ مقدار ۱۰۰ پاداش و از بلوک ۱۱ تا ۲۰ مقدار ۵۰ پاداش دریافت کرده است؛ بنابراین، کل پاداش دریافتی هر توکن ۱۵۰ خواهد بود.
Alice در تمام این مدت ۱۰۰ توکن استیک کرده است. با در نظر گرفتن ۱۵۰ پاداش به ازای هر توکن، مجموع پاداش Alice به ۱۵٬۰۰۰ توکن REWARD میرسد — یعنی دقیقاً ۷۵٪ از ۲۰٬۰۰۰ پاداشی که قرارداد در آن بازه زمانی صادر کرده است.
اگر کسی از ابتدای قرارداد در حال استیک نبوده باشد چه میشود؟
یکی از حالتهای خاص در مثال قبلی (و موردی که در بخشهای قبل نیز به آن اشاره کردیم) زمانی اتفاق میافتد که Bob تصمیم بگیرد در بلوک ۲۰ پاداش خود را دریافت کند. در این شرایط، او نیز احتمال دارد ۱۵٬۰۰۰ توکن REWARD دریافت کند، چون موجودی استیک او در بلوک ۲۰ نیز ۱۰۰ توکن است؛ دقیقاً مانند Alice.
برای حل این مشکل، باید کاری کنیم که انباشتگر پاداشها فقط از زمانی شروع به محاسبه کند که Bob سپردهگذاری کرده است.
راهحل شهودی: ثبت شماره بلوکی که Bob سپردهگذاری کرده است
یک راهحل منطقی این است که هنگام سپردهگذاری، شماره بلوک را ذخیره کنیم و بعداً پاداشها را با توجه به آن اصلاح کنیم.
اما راهحل سادهتر چیست؟
حتی سادهتر از آن، این است که محاسبه کنیم اگر Bob در همان لحظه سپردهگذاری (بلوک ۱۰) اقدام به دریافت پاداش میکرد، چقدر پاداش باید دریافت میکرد.
مثلاً در بلوک ۱۰، مقدار پاداش انباشته برای هر توکن برابر ۱۰۰ بوده است. از آنجایی که Bob تعداد ۱۰۰ توکن سپردهگذاری کرده، در تئوری میتوانسته بلافاصله ۱۰٬۰۰۰ توکن REWARD دریافت کند.
برای جلوگیری از این اتفاق، ما برای Bob یک متغیر تعریف میکنیم به نام “بدهی پاداش” (reward debt). به محض سپردهگذاری، این مقدار را برابر با موجودی سپردهگذاریشده ضربدر مقدار پاداش انباشته هر توکن قرار میدهیم.
با این کار، Bob در لحظه سپردهگذاری، هیچ پاداشی قابل دریافت نخواهد داشت چون:
پاداش فعلی – بدهی پاداش = ۰
نحوه عملکرد متغیر reward debt
ما برای Bob یک متغیر جداگانه به نام reward debt (یا “پاداشی که قبلاً فرض شده صادر شده”) تعریف میکنیم و آن را معادل همان پاداش فرضی در لحظه سپردهگذاری قرار میدهیم.
در مثال:
-
در بلوک ۱۰، پاداش انباشته برای هر توکن = ۱۰۰
-
سپرده Bob = ۱۰۰ توکن
پس reward debt برای Bob = ۱۰٬۰۰۰
اگر Bob در بلوک ۲۰ اقدام به دریافت پاداش کند، مقدار قابل دریافت برای او برابر خواهد بود با:
پاداش کل – بدهی پاداش = ۱۵٬۰۰۰ – ۱۰٬۰۰۰ = ۵٬۰۰۰
بنابراین، Bob فقط میتواند ۵٬۰۰۰ توکن REWARD در بلوک ۲۰ دریافت کند، که دقیقاً معادل سهم واقعی او از زمان ورود به استخر تا آن لحظه است.
شبه کد الگوریتم MasterChef
در ادامه، نسخهای سادهشده از الگوریتم MasterChef ارائه شده است. برای وضوح بیشتر، نام متغیرها نسبت به قرارداد اصلی تغییر داده شدهاند. همچنین برای تمرکز بر منطق اصلی، جزئیاتی مانند رویدادها (events) و نحوه مدیریت مقیاس اعشاری توکن ها حذف شدهاند.
تفاوتهای بین Synthetix و MasterChef
Synthetix و MasterChef هر دو از مکانیزم مشابهی استفاده میکنند تا بر اساس مقدار توکنی که کاربران استیک میکنند، پاداش را به ازای هر توکن انباشته کنند. تفاوت اصلی این است که بهجای ردیابی reward debt، Synthetix یک snapshot از مقدار انباشتگر پاداش (reward accumulator) در زمانی که کاربر آخرین بار با قرارداد تعامل داشته ذخیره میکند.
تفاوت بین مقدار فعلی انباشتگر پاداش و این snapshot برای محاسبه پاداش کاربر استفاده میشود.
این اختلاف به یک mapping پاداش مخصوص هر کاربر اضافه میشود و در آنجا باقی میماند تا زمانی که کاربر تابع getRewards()
را صدا بزند.
این عملیات اضافی باعث میشود الگوریتم Synthetix نسبت به MasterChef از کارایی کمتری برخوردار باشد.
سایر تفاوتها نسبتاً جزئی هستند:
-
MasterChef دارای توابع
deposit()
وwithdraw()
است.- Synthetix توابع
stake()
،withdraw()
وgetReward()
دارد.
- Synthetix توابع
-
MasterChef از شماره بلوک به عنوان واحد زمان استفاده میکند.
-
Synthetix از timestamp استفاده میکند.
-
-
همانطور که در بخشهای بالا توضیح داده شد، MasterChef پاداشها را خودش mint میکند.
-
Synthetix فرض میکند که مدیر قبلاً پاداشها را به قرارداد منتقل کرده و خودش هیچ پاداشی mint نمیکند.
-
-
MasterChef پاداشها را از یک
startBlock
قابل پیکربندی تاlastRewardBlock
توزیع میکند.-
Synthetix بهصورت hardcoded پاداشها را در طول یک هفته پس از شروع زمان توسط مدیر توزیع میکند.
Synthetix لزوماً کل موجودی پاداش در قرارداد را توزیع نمیکند، بلکه مقدار مشخصشده توسط مدیر را پرداخت میکند.
-
-
MasterChef پاداش را زمانی به کاربر منتقل میکند که کاربر
deposit()
یاwithdraw()
را با مقادیر غیر صفر صدا بزند.-
Synthetix پاداش کاربر را در یک mapping به نام
rewards
انباشته میکند اما آن را تا زمانی که کاربرgetRewards()
را صدا نزده، منتقل نمیکند.
-
-
MasterChef از چندین استخر در داخل یک قرارداد پشتیبانی میکند و پاداشها را بر اساس وزن استخر بین آنها تقسیم میکند.
-
Synthetix تنها یک استخر دارد.
-
علاقه مندان میتوانند کد SushiSwap MasterChef Staking را مطالعه کنند.
شبه کد برای الگوریتم Synthetix
نمودار زیر زیر روال ثبت و نگهداری داده ها (bookkeeping subroutine) در الگوریتم Synthetix را نشان میدهد؛ این زیر روال در زمان فراخوانی توابع deposit()
، withdraw()
یا getRewards()
اجرا میشود.
بهطور خاص، این عملیات قبل از بهروزرسانی موجودی در deposit
یا withdraw
و قبل از توزیع پاداش انجام میشود.
در نمودار زیر، متغیر lastUpdateTime
نشان دهنده آخرین زمانی است که یکی از کاربران یکی از این سه تابع را صدا زده است.
در مثال ارائه شده، کاربری که اکنون پاداش دریافت میکند، همان فردی نیست که آخرین بار با قرارداد تعامل کرده است.
علامت '
(prime) نشان میدهد که مقدار یک متغیر پس از پایان اجرای زیرروال تغییر کرده است.
خوانندگان علاقهمند میتوانند برای بررسی بیشتر، کد استیکینگ Synthetix را بهطور مستقیم مطالعه کنند.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۸ خرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس