در این بخش، موضوعات کلیدی زیر را درباره نسخه سوم پروتکل Compound بررسی خواهیم کرد:
-
نحوه ارزیابی وثیقه ها
-
جذب و تسویه وام هایی که پشتوانه کافی ندارند (لیکویید شدن)
-
نحوه فروش وثیقه های جذب شده
-
مفهوم ذخایر (Reserves)
-
و نقش ذخایر در فرآیند لیکویید شدن
اگرچه این فهرست شامل موضوعات متعددی است، اما این مفاهیم ارتباط تنگاتنگی با هم دارند. به همین دلیل، بررسی آن ها به صورت یکپارچه در یک مقاله منطقیتر و مؤثرتر خواهد بود.
پیش نیازها
خواننده پیش از مطالعه این فصل باید با مفاهیمی مانند تعریف ارزش اصلی و ارزش فعلی در Compound V3 آشنا باشد و همچنین درک پایهای از سازوکار لیکویید شدن و وثیقه گذاری در حوزه دیفای (DeFi) داشته باشد.
ساختار UserBasic در CometStorage.sol
بیایید نگاهی دوباره به ساختار UserBasic
در فایل CometStorage.sol
بیندازیم.
اگر مقدار principal
(کادر آبی) منفی باشد، این موضوع نشان میدهد که کاربر یک وام گیرنده است و این مقدار منفی، بیانگر ارزش اصلی بدهی او خواهد بود.
فیلد assetsIn
(کادر قرمز) بهصورت یک بیت مپ (Bitmap) طراحی شده تا مشخص کند آیا کاربر یک دارایی خاص را به عنوان وثیقه سپرده گذاری کرده یا نه. در زمان نگارش این مقاله، ترتیب قرارگیری بیت ها در این بیت مپ به شکل زیر تعریف شده است:
دو متغیر baseTrackingIndex
و baseTrackingAccrued
برای پیگیری و ثبت توزیع پاداش ها بهکار میروند. این موضوع را در یک مقاله جداگانه بررسی خواهیم کرد. متغیر _reserved
در حال حاضر کاربرد خاصی ندارد.
ساختار UserBasic
اطلاعاتی درباره میزان وثیقه ای که کاربر نگه میدارد ارائه نمیدهد. برای مشاهده موجودی وثیقه، باید به متغیر balance
در ساختار UserCollateral
مراجعه کنیم که در mapping
تو در توی userCollateral
ذخیره شده است. متغیر _reserved
نیز بدون استفاده باقی مانده است.
برای مشاهده فهرست وثیقه هایی که یک کاربر در سیستم ثبت کرده، ابتدا باید در بازه ۰ تا numAssets
که در Compound ذخیره شده، حلقه بزنیم و بررسی کنیم که آیا بیت مربوط به هر دارایی برای آن کاربر مقدار یک دارد یا نه. اگر مقدار بیت برابر با یک باشد، یعنی کاربر آن دارایی را به عنوان وثیقه سپرده گذاری کرده است.
در این صورت، آدرس توکنی که با آن بیت مرتبط است را به دست می آوریم و سپس به سراغ متغیر userCollateral[user][collateralAsset]
می رویم تا مقدار دارایی وثیقه ای که کاربر نگه می دارد را بررسی کنیم.
برای محاسبه ارزش دلاری این وثیقه، مقدار موجودی را در قیمت همان دارایی طبق اوراکل ضرب می کنیم. جدول زیر یک مثال از نحوه جمع زدن ارزش کل وثیقه های کاربر را نشان می دهد:
ساختار AssetInfo
آدرس اوراکلی که Compound از آن برای دریافت قیمت وثیقه استفاده می کند، در ساختار AssetInfo
ذخیره شده است (کادر آبی).
به تصویر بالا نگاه کنید؛ ساختار AssetInfo
مجموعاً ۴۳۲ بیت فضا اشغال می کند، یعنی برای ذخیره آن به دو اسلات (Slot) در حافظه نیاز داریم. در بخش بعدی دوباره به این موضوع بازخواهیم گشت و آن را با جزئیات بیشتری بررسی خواهیم کرد.
نمایش ساختار AssetInfo در رابط کاربری بازارهای Compound Finance
بیایید محتوای ساختار AssetInfo
را که پیشتر بررسی کردیم، با اطلاعاتی که در رابط کاربری (UI) بازارهای Compound Finance نمایش داده می شود، مقایسه کنیم. در این رابط، بیشتر متغیرهای موجود در AssetInfo
بهوضوح قابل مشاهده هستند.
مستندات و کدها توضیح دقیقی درباره کاربرد متغیر scale
ارائه نمی دهند، اما این مقدار معمولاً برابر با 1e18
است. به همین دلیل، می توان حدس زد که این متغیر برای تعیین مقیاس درصدها و تنظیم دقت محاسبات در اختیار کاربران قرار گرفته است.
برای نمونه، زمانی که اطلاعات مربوط به توکن UNI با شناسه دارایی assetId = 3
را از طریق لینک Etherscan استعلام می کنیم و تابع getAssetInfo()
را فراخوانی می کنیم، می توانیم مقادیر بازگشتی را با داده های نمایش داده شده در مارکت Compound مقایسه کنیم. رابطه بین آنها بهخوبی قابل مشاهده است.
یک نکته مهم
جریمه لیکویید شدن برابر است با: penalty = 1 – liquidation factor
متغیر liquidateCollateralFactor
نشان می دهد که در چه نسبت وامی (LTV) یک وام قابل لیکویید شدن است، در حالی که liquidationFactor
میزان جریمه لیکویید شدن را رمزگذاری می کند.
نکته گیج کننده اینجاست که متغیر liquidationFactor
در ساختار کد، مفهومی متفاوت از liquidation factor
در رابط کاربری دارد.
در تصویر بالا، ابتدا اسکرین شاتی از بازار UNI در UI مشاهده می شود و سپس داده های بازگشتی از تابع getAssetInfo()
برای همین توکن در Etherscan نمایش داده شده اند.
این مقایسه، ارتباط میان پارامترهای وثیقه UNI در رابط Compound و داده های آنچین را بهخوبی نشان می دهد.
سوال منطقی بعدی این است:
Compound اطلاعات مربوط به ساختارهای AssetInfo
را کجا ذخیره می کند؟
ذخیره سازی ساختارهای AssetInfo در متغیرهای غیرقابل تغییر (Immutable)
در پروتکل Compound V3، اطلاعات مربوط به هر دارایی (AssetInfo) به جای اینکه در حافظه (storage) ذخیره شوند، در متغیرهای غیرقابل تغییر (immutable) قرار می گیرند. این روش به شکل چشمگیری باعث صرفه جویی در مصرف گس می شود.
از آنجایی که ذخیره کردن ساختار AssetInfo
نیاز به دو کلمه ۳۲ بایتی دارد (مجموعاً ۶۴ بایت برای ۴۳۲ بیت اطلاعات)، کامپاند نامگذاری خاصی برای این متغیرها در نظر گرفته است. برای هر دارایی، دو متغیر به صورت assetXX_a
و assetXX_b
تعریف می شوند که در آن XX
نشان دهنده شماره اندیس آن دارایی است. مثلاً asset00_a
و asset00_b
به طور مشترک داده های مربوط به دارایی شماره ۰ را در خود نگه می دارند.
این متغیرها به صورت tightly packed و بدون فاصله ذخیره می شوند و به همین دلیل برای استخراج داده، نیاز به عملگرهای بیت شیفت و mask وجود دارد. حال می توانیم به سراغ پیاده سازی تابع getAssetInfo()
در فایل Comet.sol
بین خطوط 280 تا 356 برویم.
این تابع صرفاً داده های بسته بندی شده در متغیرهای immutable را باز می کند و آنها را به شکل یک ساختار AssetInfo
بازمی گرداند. از آنجایی که منطق بیت شیفت (bitshifting) و باز کردن داده ها کاملاً ساده است، نیازی به توضیح جزئیات آن در اینجا وجود ندارد.
از آنجایی که این متغیرها غیرقابل تغییر هستند، اگر توسعه دهندگان بخواهند دارایی جدیدی به سیستم اضافه کنند یا پارامترهای مربوط به یک دارایی موجود را تغییر دهند، باید یک پیاده سازی جدید از قرارداد حاکمیتی ایجاد کنند و آدرس پروکسی را به نسخه جدید بهروزرسانی کنند.
در این فرایند باید دقت شود که دارایی های جدید فقط به انتهای لیست اضافه شوند و تعاریف قبلی به هیچ وجه دستکاری نشوند، چون ترتیب متغیرهای immutable ثابت است و هرگونه تغییر در ترتیب باعث بروز رفتارهای نادرست در قرارداد خواهد شد.
بررسی اینکه آیا یک وام گیرنده قابل لیکویید شدن است
در فایل Comet.sol
تابعی به نام isLiquidatable() وجود دارد که بررسی می کند آیا کاربر شرایط لیکویید شدن را دارد یا نه. این تابع ابتدا ارزش هر دارایی وثیقه ای را که کاربر نگه می دارد، در ضریب لیکویید شدن (liquidationFactor) مربوط به آن دارایی ضرب می کند. سپس مجموع این مقادیر را با ارزش فعلی بدهی کاربر مقایسه می کند. اگر این مجموع کمتر از بدهی فعلی کاربر (که عددی منفی است) باشد، آنگاه کاربر قابل لیکویید شدن خواهد بود.
یعنی حتی اگر یکی از وثیقه های کاربر از حد لیکویید شدن عبور کرده باشد، تا زمانی که سایر وثیقه ها بتوانند این کمبود را جبران کنند، کاربر لیکویید نخواهد شد. بهعبارت دیگر، ارزش کامل وثیقه ها به حساب نمی آید — بلکه هر وثیقه با ضریب تخفیف لیکویید شدن محاسبه می شود. این ضریب تضمین می کند که همیشه حاشیه امنی برای پروتکل وجود داشته باشد.
در ادامه، همان مثال قبلی را می بینید که ارزش واقعی وثیقه های کاربر را بعد از اعمال ضریب لیکویید شدن نشان می دهد:
در مثال بالا، وامگیرنده فرضی زمانی لیکویید میشود که مانده وام او از ۸۳۶۰ دلار بیشتر شود.
لیکویید کردن یک وامگیرنده (Absorb)
اگر تابع isLiquidatable()
مقدار true برگرداند، یعنی کاربر قابل لیکویید شدن است و پروتکل میتواند وثیقه او را جذب کند. کاری که برخی از پروتکلها «liquidation» مینامند، در Compound V3 تحت عنوان «جذب» (absorb) شناخته میشود.
در Compound V3 عملیات جذب بهصورت کامل و یکجا انجام میشود — یعنی امکان لیکویید کردن بخشی از وثیقه وجود ندارد. وقتی absorb اجرا شود، تمام موجودی دارایی های وثیقه ای کاربر صفر میشود.
مثال جذب وثیقه (Absorb)
فرض کنید باب ۱۰۰۰ دلار ETH به Compound V3 واریز کرده و در مقابل آن ۸۰۰ دلار USDC وام گرفته است. نسبت وثیقه گذاری (Collateralization Ratio) در این حالت ۸۰ درصد است که در محدوده مجاز قرار دارد.
حال ارزش ETH کاهش پیدا میکند و به ۸۸۰ دلار میرسد. این کاهش باعث میشود نسبت وام به وثیقه (LTV) به ۹۰.۹٪ برسد و آستانه لیکویید شدن که ۹۰٪ است را رد کند.
در این لحظه، یک لیکوییدکننده تابع absorb()
را روی حساب باب فراخوانی میکند و تمام وثیقه ETH به ارزش ۸۸۰ دلار به داخل پروتکل جذب میشود.
اکنون فرض کنیم جریمه لیکویید شدن برابر با ۵٪ باشد.
از آنجا که وثیقه فعلی ۸۸۰ دلار است، مقدار ۵٪ آن برابر است با ۴۴ دلار ETH.
پروتکل این ۴۴ دلار را بهعنوان جریمه کسر میکند و ۸۳۶ دلار باقی میماند.
از آنجایی که باب ۸۰۰ دلار USDC وام گرفته، این مقدار برای بازپرداخت بدهی برداشت میشود و ۳۶ دلار مازاد باقی میماند.
این ۳۶ دلار مازاد به باب برگردانده میشود — در نتیجه، باب اکنون بهعنوان یک وام دهنده (lender) در سیستم شناخته میشود که ۳۶ دلار USDC سپرده دارد.
با توجه به اینکه باب قبلاً ۸۰۰ دلار USDC را هنگام دریافت وام برداشت کرده بود، اکنون مجموع دارایی نهایی او برابر با ۸۳۶ دلار است (۳۶ دلار سپرده و ۸۰۰ دلار وام مصرفشده).
نکات مهم:
-
در اجرای تابع
absorb()
هیچ پاداش مستقیمی برای لیکوییدکننده در نظر گرفته نشده است. -
زمانی که کاربر لیکویید میشود، اگر ارزش وثیقه از بدهی بیشتر باشد، کاربر بهطور خودکار به یک وام دهنده تبدیل میشود.
-
اگر وثیقه برای پوشش بدهی کافی نباشد، پروتکل آن کسری را از محل ذخایر (reserves) خود تأمین میکند — که در بخش بعدی به آن خواهیم پرداخت.
ذخایر (Reserves)
در Compound V3، بخشی از سودی که وام گیرندگان پرداخت میکنند اما به وام دهندگان تعلق نمیگیرد، بهعنوان ذخایر (Reserves) در نظر گرفته میشود.
مثال ذخایر
فرض کنیم آلیس ۱۰۰ دلار USDC به پروتکل وام میدهد و در ازای آن ۵٪ سود دریافت میکند. از سوی دیگر، باب همین مقدار یعنی ۱۰۰ دلار USDC را از پروتکل وام میگیرد و باید ۱۰٪ سود پرداخت کند. برای ساده سازی، فرض میکنیم فقط آلیس و باب در سیستم فعال هستند. در این حالت، باب ۱۰٪ سود پرداخت کرده اما آلیس فقط ۵٪ سود دریافت کرده است. تفاوت این دو یعنی ۵٪ اضافی به عنوان ذخیره نزد پروتکل باقی میماند. این مبلغ اضافی را reserve مینامیم — بخشی از سود سیستم که برای تقویت پایداری مالی پروتکل ذخیره میشود و به کاربران بازگردانده نمیشود.
در سازوکار Compound V3، مهم نیست که باب تاکنون ۱۱۰ دلار USDC (اصل وام + بهره) را به پروتکل پرداخت کرده باشد یا نه. تا زمانی که بدهی ثبت شده باقی است، باب ۱۱۰ دلار USDC به پروتکل بدهکار است، و در مقابل، پروتکل ۱۰۵ دلار USDC به آلیس بدهکار است. بنابراین، در این وضعیت ۵ دلار USDC به عنوان ذخیره (Reserve) در سیستم باقی میماند.
حال فرض کنیم باب وام را کامل پرداخت میکند. پروتکل اکنون موجودی ۱۱۰ دلار USDC دارد، که ۱۰۵ دلار آن متعلق به آلیس است. همچنان ۵ دلار باقیمانده در حساب پروتکل به عنوان ذخیره باقی میماند — یعنی چیزی تغییر نکرده است.
محاسبه مقدار ذخایر: تابع getReserves()
تابع getReserves()
مقدار فعلی ذخایر پروتکل را باز میگرداند. این مقدار به صورت زیر محاسبه میشود:
مقدار کل USDC “تحت مالکیت پروتکل” برابر است با:
-
موجودی فعلی توکن USDC در قرارداد:
ERC20(baseToken).balanceOf(address(this))
-
بهعلاوه ارزش فعلی کل بدهی ها (totalBorrow)
-
منهای ارزش فعلی کل سپرده ها (totalSupply) که پروتکل به وام دهندگان بدهکار است.
درواقع فرمول به سکل زیر است:
reserves = usdc_balance + totalBorrow – totalSupply
در کد تابع getReserves()
مشاهده میکنید که totalSupply
با علامت منفی وارد محاسبه میشود، چون این مقدار بدهی پروتکل به وامدهندگان است. دو جزء مثبت این معادله — موجودی واقعی USDC در قرارداد و مقدار خالص بدهی وامگیرندگان به پروتکل — بیانگر مقدار واقعی USDC هستند که پروتکل در مالکیت خود دارد.
بررسی مقدار ذخایر با استفاده از تابع getReserves()
اگر به تابع getReserves() در Etherscan مراجعه کنیم، خواهیم دید که در زمان نگارش این مقاله، مقدار ذخایر پروتکل حدود ۳.۴۷ میلیون دلار USDC است (با دقت ۶ رقم اعشار).
در صورتی که بازار USDC در شبکه Mainnet را از طریق رابط کاربری بازارهای Compound V3 نیز مشاهده کنیم، در قسمت نمایش اطلاعات بازار، همین عدد یعنی ۳.۴۷ میلیون دلار ذخیره به کاربر نشان داده میشود.
کاهش ذخایر پس از اجرای absorb()
اگر تابع getReserves()
را قبل و بعد از اجرای absorb()
روی یک وامگیرنده فراخوانی کنیم، متوجه خواهیم شد که مقدار ذخایر کاهش پیدا کرده است. این کاهش به دو دلیل اتفاق میافتد:
1) پروتکل بدهی را پرداخت کرده است: زمانی که یک کاربر لیکویید میشود، پروتکل با استفاده از وثیقه او بدهیاش را تسویه میکند. این عمل باعث میشود که پروتکل دیگر بخشی از آن بدهی را در تراز مالی خود نداشته باشد — در نتیجه، بخشی از ذخایر خرج شده و کاهش مییابد.
2) کاربر لیکویید شده تبدیل به وام دهنده میشود: اگر ارزش وثیقه جذبشده بیشتر از مقدار بدهی باشد، مازاد آن بهصورت یک سپرده جدید در حساب کاربر ثبت میشود. از آن لحظه به بعد، پروتکل باید این سپرده را به کاربر بازگرداند، یعنی اکنون بدهی جدیدی به عنوان وامدهنده دارد، که این نیز موجب کاهش ذخایر میشود.
تابع withdrawReserves()
مقدار ذخایر مازاد که از سود حاصل از تفاوت نرخ بهره میان وام دهندگان و وام گیرندگان تشکیل شده، در اختیار نهاد حاکمیتی (governance) پروتکل قرار دارد. این ذخایر میتوانند بنا به تصمیم حاکمیت، برداشت شوند.
برای این کار، از تابع زیر استفاده میشود:
هدف ذخایر (Target Reserves)
در قرارداد Comet.sol، متغیری عمومی و غیرقابل تغییر (immutable) به نام targetReserves
تعریف شده است که مقدار هدف ذخایر را مشخص میکند.
زمانی که مقدار targetReserves را در Etherscan بررسی میکنیم، مشاهده میکنیم که این مقدار برابر با ۵ میلیون USDC است.
متغیر targetReserves
تنها یک کاربرد مشخص در کل پروتکل دارد:
تعیین اینکه آیا پروتکل حاشیه امن مالی کافی (margin of safety) برای نفروختن وثیقه جذب شده را دارد یا نه.
بهعبارت دیگر، اگر مقدار ذخایر فعلی از مقدار targetReserves
بیشتر باشد، پروتکل ترجیح میدهد وثیقه جذب شده را نگه دارد، به این امید که قیمت آن در آینده افزایش پیدا کند، و آن را بلافاصله به فروش نرساند.
در ادامه به بررسی تنها تابعی میپردازیم که از این متغیر استفاده میکند تا مشخص شود این منطق چگونه در کد پیاده سازی شده است.
تابع buyCollateral()
وثیقه پس از اجرای absorb همچنان داخل پروتکل باقی میماند. تنها اتفاقی که میافتد این است که موجودی وثیقه ای کاربر به صفر میرسد — اما خود دارایی های وثیقه ای به هیچ جای دیگری منتقل نمیشوند. این دارایی ها همچنان “داخل” Compound V3 باقی میمانند.
برای ایجاد انگیزه در لیکوییدکننده ها، پروتکل این وثیقه های نگهداشتهشده را با تخفیف از طریق تابع buyCollateral() به فروش میگذارد.
در این تابع دو منطق تجاری بسیار مهم وجود دارد:
۱) اگر مقدار ذخایر فعلی بیشتر از مقدار هدف ذخایر (۵ میلیون دلار) باشد، این تابع برگشت میزند (revert) و اجازه خرید وثیقه به لیکوییدکننده ها نمیدهد. (کادر زرد در کد زیر) همانطور که پیشتر گفته شد، Compound ترجیح میدهد در شرایطی که ذخایر کافی دارد، دارایی های جذب شده را نگه دارد تا شاید در آینده ارزش آنها افزایش پیدا کند. چون در حال حاضر وضعیت نقدینگی پروتکل مطلوب است، نیازی به تبدیل وثیقه به پول نقد نمیبیند.
۲) نرخ تبادل فروش وثیقه توسط تابع quoteCollateral()
تعیین میشود. (کادر قرمز در کد زیر)
سایر بخشهای کد به اندازه کافی گویا هستند و نیاز به توضیح ندارند.
ربات لیکوییدکننده (Liquidation bot)
برای لیکویید کردن یک وام گیرنده، لیکوییدکننده باید ابتدا تابع absorb()
را با آدرس حساب وام گیرنده به عنوان آرگومان فراخوانی کند، و سپس در همان تراکنش تابع buyCollateral()
را اجرا نماید.
لیکوییدکننده باید قبل از اقدام، بررسی کند که:
-
ذخایر فعلی پروتکل بیشتر از مقدار هدف ذخایر (
targetReserves
) نباشد -
حساب موردنظر واقعاً قابل لیکویید شدن باشد، که از طریق تابع
isLiquidateable()
قابل بررسی است
پروتکل Compound V3 یک ربات لیکوییدکننده مرجع (reference liquidator bot) نیز ارائه داده است. اما توجه داشته باشید که این فقط یک پیاده سازی مرجع است.
رقابت برای انجام لیکوییدیشن پیش از سایر رباتها بسیار شدید است. بنابراین اگر قصد دارید از این مکانیزم سودآوری کنید، باید کد خود را بهشدت از نظر مصرف گس بهینه سازی کنید تا بتوانید سریع تر و با هزینه کمتر پوزیشن ها را لیکویید کرده و سود واقعی به دست آورید.
خلاصه
فهرست دارایی هایی که Compound V3 بهعنوان وثیقه میپذیرد — همراه با پارامترهایی مانند نسبت وثیقه گذاری (collateralization ratio)، نسبت لیکویید شدن (liquidation ratio)، آدرس اوراکل و سایر مشخصات — همگی بهصورت فشرده (packed) در متغیرهای غیرقابل تغییر (immutable) ذخیره میشوند. برای اعمال هرگونه تغییر در این مقادیر، نیاز به بهروزرسانی پراکسی قرارداد Compound V3 وجود دارد.
لیکوییدیشن در Compound V3 بهصورت یکجا و کامل انجام میشود — یعنی یا کل پوزیشن کاربر لیکویید میشود یا هیچ چیز. زمانی که کاربری لیکویید شود، درصدی از وثیقه او به اندازه 1 - liquidationRatio
از بین میرود (بهعنوان جریمه)، و باقیمانده برای تسویه بدهی استفاده میشود. هر مقدار اضافه نیز به کاربر تعلق میگیرد و بهعنوان مانده مثبت برای او ثبت میشود.
پروتکل پس از جذب وثیقه، مالک دارایی مازاد میشود. این وثیقه را طبق نرخ تعیینشده توسط تابع quoteCollateral()
، با تخفیف برای فروش ارائه میدهد. اما اگر مقدار ذخایر فعلی بیشتر از targetReserves
باشد، پروتکل از فروش وثیقه خودداری میکند.
ذخایر (reserves) برابرند با مجموع پولی که وام گیرندگان به پروتکل بدهکارند بهعلاوه موجودی USDC پروتکل، منهای مقدار بدهی پروتکل به وام دهندگان. این پول توسط حاکمیت قابل برداشت است.
اگر این مقاله برایتان جالب بوده اما هنوز با مفاهیم برنامه نویسی یا قراردادهای هوشمند آشنایی ندارید، نگران نباشید. ما یک بخش ویژه برای آموزش برنامه نویسی آماده کردهایم که از صفر و با زبان ساده، شما را برای ورود به دنیای دیفای و بلاکچین آماده میکند. همین حالا شروع کنید و قدم به قدم مسیر توسعه را یاد بگیرید.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۳۰ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس