آموزش وام فوری در سالیدیتی

وام فوری در سالیدیتی، نوعی وام بین قراردادهای هوشمند به شمار می روند که باید در همان تراکنش بازپرداخت شوند. در این مقاله، علاوه بر استاندارد ERC3156 و ساختار وام های فوری، راهکارهایی بررسی می شوند که ارائه دهندگان وام و وام گیرندگان در آن ها ممکن است هدف حمله قرار بگیرند، نکاتی که دانستن آن ها برای توسعه دهندگان قراردادهای هوشمند و علاقه مندان به آموزش برنامه نویسی در حوزه دیفای اهمیت زیادی دارد.

ابتدا، یک مثال بسیار ساده از نحوه عملکرد وام فوری ارائه می کنیم:

مثال وام فوری

اگر وام گیرنده، وام را بازپرداخت نکند، دستور require با پیام “flash not paid back” باعث می شود کل تراکنش بازگردانده شود (revert).

فقط قراردادهای هوشمند می توانند از وام های فوری استفاده کنند

یک کیف پول معمولی (EOA) نمی تواند تابعی را فراخوانی کند که وام فوری دریافت کند و در همان تراکنش، توکن ها را بازگرداند. برای استفاده از وام های فوری، باید از یک قرارداد هوشمند جداگانه استفاده کرد.

وام های فوری نیازی به وثیقه ندارند

اگر یک وام فوری به درستی پیاده سازی شده باشد (که این موضوع بسیار مهم است)، هیچ ریسکی برای عدم بازپرداخت وام وجود ندارد، زیرا اگر شرط require برقرار نباشد یا تراکنش با خطا مواجه شود، کل تراکنش لغو می شود و اتر منتقل نخواهد شد.

کاربردهای وام های فوری چیست؟

آربیتراژ

رایج ترین کاربرد وام فوری، انجام معاملات آربیتراژ است. به عنوان مثال، فرض کنید قیمت اتریوم (Ether) در یک استخر معاملاتی 1200 دلار باشد و در یک پلتفرم دیفای (DeFi) دیگر 1300 دلار معامله شود. در این شرایط، فرصت مناسبی برای خرید اتریوم از استخر اول و فروش آن در پلتفرم دوم با 100 دلار سود ایجاد می شود. اما برای انجام این معامله ابتدا به سرمایه نیاز دارید تا بتوانید اتریوم را خریداری کنید. در اینجا وام فوری بهترین راه حل است، زیرا نیازی نیست 1200 دلار از پیش در اختیار داشته باشید. می توانید 1200 دلار اتریوم وام بگیرید، آن را به قیمت 1300 دلار بفروشید، 1200 دلار وام را بازپرداخت کنید و 100 دلار سود (پس از کسر کارمزدها) برای خود نگه دارید.

بازپرداخت وام ها با نرخ بهتر

در وام های معمولی دیفای، معمولا باید وثیقه ای ارائه دهید. به عنوان مثال، اگر بخواهید 10 هزار دلار استیبل کوین وام بگیرید، باید معادل 15 هزار دلار اتریوم به عنوان وثیقه سپرده کنید.

حالا فرض کنید نرخ بهره وام فعلی شما 5 درصد است و قصد دارید آن را با یک قرارداد وام دهی دیگر که نرخ بهره 4 درصد دارد جایگزین کنید. برای این کار باید مراحل زیر را انجام دهید:

  1. بازپرداخت 10 هزار دلار استیبل کوین

  2. برداشت 15 هزار دلار اتریوم وثیقه

  3. واریز 15 هزار دلار اتریوم به قرارداد جدید

  4. دریافت مجدد وام 10 هزار دلاری با نرخ بهره کمتر

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

تعویض وثیقه

در مثال قبلی، وام گیرنده 15 هزار دلار اتریوم را به عنوان وثیقه استفاده می کرد. حال فرض کنید پروتکل دیگری نسبت وثیقه بهتری را در ازای wBTC (بیت کوین رپ شده) ارائه دهد. در این حالت، وام گیرنده می تواند با استفاده از یک وام فوری و مجموعه مراحلی مشابه آنچه در بالا توضیح داده شد، وثیقه خود را از اتریوم به wBTC تغییر دهد، بدون آنکه نیازی به بازپرداخت اصل وام داشته باشد.

تسویه وام گیرندگان (Liquidation)

در حوزه وام های دیفای، اگر ارزش وثیقه به زیر یک سطح مشخص برسد، پروتکل می تواند آن را تسویه کند، یعنی به اجبار بفروشد تا بدهی وام را پوشش دهد. برای مثال، اگر ارزش اتریوم به 12 هزار دلار کاهش پیدا کند، ممکن است پروتکل به یک فرد اجازه دهد این اتریوم را به قیمت 11,500 دلار خریداری کند، به شرط آنکه ابتدا وام 10 هزار دلاری را بازپرداخت کند.

در این سناریو، یک تسویه کننده (Liquidator) می تواند با استفاده از وام فوری، 10 هزار دلار استیبل کوین وام بگیرد، وام را بازپرداخت کند و در ازای آن 11,500 دلار اتریوم دریافت کند. سپس این اتریوم را در یک صرافی دیگر به استیبل کوین تبدیل می کند و وام فوری را تسویه می کند.

افزایش بازدهی برای سایر برنامه های دیفای

پروتکل هایی مانند یونی سواپ (Uniswap) و آوه (AAVE) از طریق کارمزد معاملات یا سود وام دهی، درآمد کسب می کنند. اما از آنجایی که این پروتکل ها سرمایه بسیار زیادی در اختیار دارند، می توانند با ارائه وام های فوری، درآمد اضافی نیز ایجاد کنند. این موضوع باعث افزایش بهره وری سرمایه می شود، چرا که اکنون یک منبع سرمایه می تواند برای چندین هدف مورد استفاده قرار گیرد.

ایجاد یک چرخه اهرمی (Leverage Loop) در یک تراکنش واحد

با استفاده از پروتکل های وام دهی، می توان موقعیت های لانگ و شورت اهرمی (leveraged longs and shorts) ایجاد کرد. به عنوان مثال، برای باز کردن یک موقعیت لانگ اهرمی روی ETH، کاربر می تواند مقدار مشخصی ETH را به عنوان وثیقه در یک استخر وام دهی واریز کند، سپس یک استیبل کوین وام بگیرد، استیبل کوین را به ETH تبدیل کند و مجددا ETH جدید را به استخر وام دهی واریز کند. این فرآیند می تواند چندین بار تکرار شود. در نهایت، مجموع ارزش وثیقه و مقدار ETH وام گرفته شده، از مقدار اولیه بیشتر خواهد بود و در نتیجه کاربر در برابر تغییرات قیمت ETH، اهرم بیشتری به دست می آورد.

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

مجموع دارایی هایی که به این روش می توان وام گرفت، عبارت است از:

مجموع دارایی هایی که می توان وام گرفت

در اینجا LTV (نسبت وام به ارزش وثیقه یا Loan-to-Value) حداکثر نسبتی است که پروتکل اجازه می دهد وام دریافت کنید. برای مثال، اگر پروتکل برای دریافت 800 دلار ETH نیاز به سپرده گذاری 1000 دلار استیبل کوین داشته باشد، در این صورت LTV برابر است با 0.8 = 1000/800.
بنابراین کاربر می تواند تا سقف 5 برابر مقدار سپرده اولیه خود، در معرض تغییرات قیمت ETH قرار بگیرد، یعنی 1 / (1 – 0.8) = 5. به عبارتی، با یک سپرده 1000 دلاری می تواند معادل 5000 دلار ETH در اختیار داشته باشد.

روش بهتر

به جای انجام همه این مراحل به صورت حلقه ای (که می تواند هزینه گس بالایی داشته باشد)، می توان از روش زیر استفاده کرد:

  • ابتدا با استفاده از یک وام فوری، 5000 دلار استیبل کوین وام بگیرد

  • استیبل کوین ها را به معادل 5000 دلار ETH تبدیل کند

  • ETH را به عنوان وثیقه در استخر وام دهی سپرده گذاری کند

  • 4000 دلار استیبل کوین از همان استخر وام بگیرد

  • 1000 دلار استیبل کوین شخصی خود را به 4000 دلار استیبل کوین وام گرفته شده اضافه کند و وام فوری را تسویه کند

در نتیجه، اکنون کاربر 5000 دلار ETH به عنوان وثیقه در اختیار دارد و 4000 دلار استیبل کوین از استخر وام گرفته است.

هک قراردادهای هوشمند

وام های فوری بیشتر به خاطر استفاده آن ها توسط هکرهای کلاه سیاه برای سوء استفاده از پروتکل ها شناخته شده اند. دو بردار حمله (Attack Vector) اصلی در این حوزه، دستکاری قیمت و دستکاری حاکمیت (رای گیری) هستند. زمانی که این نوع حملات روی برنامه های دیفای با مکانیزم های دفاعی ضعیف انجام شوند، مهاجمان می توانند با خرید گسترده یک دارایی، قیمت آن را به طور مصنوعی افزایش دهند، یا تعداد زیادی توکن دارای حق رای به دست آورند و یک پیشنهاد حاکمیتی را به نفع خود تصویب کنند.

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

نمونه هایی از حملات وام فوری

حملات مبتنی بر وام های فوری، از رایج ترین انواع سوء استفاده ها در فضای دیفای محسوب می شوند، چرا که بسیاری از توسعه دهندگانی که از فضای وب 2 وارد این حوزه می شوند، معمولا با این نوع تهدیدات آشنایی کافی ندارند. در ادامه چند نمونه از معروف ترین این حملات آمده است:

استفاده از وام های فوری برای هک پروتکل ها خود یک موضوع جداگانه است. تمرکز این مقاله بر روی پیاده سازی های ناامن در قراردادهای ارائه دهنده و دریافت کننده وام های فوری است.

پروتکل ERC3156

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

مشخصات دریافت کننده ERC3156

اولین بخش از این استاندارد به رابطی مربوط می شود که وام گیرنده باید آن را پیاده سازی کند. این رابط در ادامه نمایش داده شده است.
وام گیرنده تنها لازم است یک تابع را پیاده سازی کند.

erc3156 receiver

در اینجا به توضیح آرگومان های این تابع می پردازیم.

initiator (مبدا تراکنش)

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

تابع onFlashLoan را قرارداد وام دهنده فراخوانی می کند، نه initiator. بنابراین باید درون تابع onFlashLoan() بررسی کنید که msg.sender همان قرارداد وام دهنده باشد، زیرا این تابع external است و هر کسی می تواند آن را اجرا کند.

در واقع، initiator همان msg.sender یا قرارداد وام دهنده نیست. این آدرس مشخص می کند که چه کسی قرارداد وام دهنده را وادار کرده است تا تابع onFlashLoan را فراخوانی کند.

token (توکن وام گرفته شده)

این پارامتر، آدرس قرارداد توکن ERC20 است که وام می گیرید. معمولا قراردادهای ارائه دهنده وام های فوری، چندین نوع توکن در اختیار دارند. استاندارد ERC3156 به طور پیش فرض از وام فوری برای اتر (Ether) پشتیبانی نمی کند. با این حال، می توانید با دریافت WETH و باز کردن آن (unwrap)، این کار را انجام دهید.

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

fee (کارمزد وام)

این پارامتر مقدار توکن لازم برای پرداخت کارمزد وام را مشخص می کند. این مقدار به صورت عدد مطلق تعریف می شود و درصدی نیست.

data (داده های ورودی)

اگر قرارداد وام گیرنده رفتار ثابتی برای دریافت وام نداشته باشد، می توانید با پارامتر data رفتار آن را تعیین کنید. برای مثال، در قراردادهای آربیتراژ می توانید با این پارامتر مشخص کنید که با کدام استخرها معامله انجام دهد.

مقدار بازگشتی

تابع باید مقدار keccak256("ERC3156FlashBorrower.onFlashLoan") را بازگرداند. در ادامه مقاله، دلایل این الزام توضیح داده می شود.

پیاده سازی مرجع برای وام گیرنده

نمونه کد این بخش نسخه ساده شده از کد استاندارد ERC3156 است. در این پیاده سازی، قرارداد به قرارداد وام دهنده اعتماد کامل دارد. اگر قرارداد وام دهنده آسیب پذیر باشد یا آن را دستکاری کنند، مهاجم می تواند با پارامترهای اشتباه (مانند amount، fee یا initiator) به این قرارداد حمله کند.

زمانی که قرارداد وام دهنده به صورت غیرقابل تغییر (immutable) نوشته شود، این نگرانی از بین می رود. اما اگر این قرارداد قابلیت ارتقاء داشته باشد، چنین حمله ای امکان پذیر خواهد بود.

پیاده‌سازی وام‌گیرنده سریع erc 3156

مشخصات قرارداد وام دهنده ERC3156

در ادامه، رابط (interface) مربوط به قرارداد وام دهنده طبق استاندارد ERC3156 ارائه می شود:

وام دهنده erc3156

پارامترهای این رابط همان مفاهیمی را دارند که در بخش قبل توضیح داده شد، بنابراین در اینجا نیازی به تکرار آن ها نیست.

تابع flashLoan() باید چند عملیات مهم را انجام دهد:

  • ممکن است فردی تابع flashLoan() را با توکنی فراخوانی کند که قرارداد وام دهنده از آن پشتیبانی نمی کند. باید این موضوع بررسی شود.

  • ممکن است فردی مقدار درخواستی برای وام را بزرگتر از مقدار مجاز (maxFlashLoan) تعیین کند. این مورد نیز باید بررسی شود.

  • پارامتر data باید به همان صورت به گیرنده منتقل شود.

مهم تر از همه، تابع flashLoan() باید توکن ها را به گیرنده منتقل کند و اطمینان حاصل کند که آن ها بازگردانده می شوند. نباید صرفا به این موضوع اعتماد کند که وام گیرنده به طور دستی توکن ها را باز می گرداند. دلیل این موضوع را در بخش بعدی توضیح می دهیم.

در ادامه، پیاده سازی مرجع (reference implementation) که در مستندات EIP 3156 آمده است، نقل شده تا بخش های مهم آن را بهتر توضیح دهیم:

erc 3156 transfer back

توجه داشته باشید که در پیاده سازی مرجع، فرض بر این است که توکن های ERC20 در صورت موفقیت، مقدار true باز می گردانند. اما همه توکن ها این رفتار را ندارند. بنابراین، اگر از توکن های ERC20 ناسازگار استفاده می کنید، باید از کتابخانه SafeTransfer استفاده کنید.

ملاحظات امنیتی

کنترل دسترسی و اعتبارسنجی ورودی در قرارداد وام گیرنده

قرارداد هوشمند وام گیرنده باید به گونه ای طراحی شود که تنها قرارداد وام دهنده بتواند تابع onFlashLoan() را فراخوانی کند. در غیر این صورت، بازیگران دیگری می توانند این تابع را اجرا کنند و رفتار غیرمنتظره در قرارداد ایجاد کنند.

علاوه بر این، هر کسی می تواند تابع flashLoan() را با یک وام گیرنده دلخواه به عنوان هدف و با داده های دلخواه فراخوانی کند. برای جلوگیری از سوء استفاده، قرارداد دریافت کننده وام باید فقط اجازه دهد مجموعه مشخصی از initiator ها این فرآیند را آغاز کنند.

قفل های جلوگیری از حملات بازدرآمد (Reentrancy Locks) بسیار حیاتی هستند

براساس تعریف ERC3156، این استاندارد نمی تواند از الگوی “بررسی-تأثیرات” (check-effects) برای جلوگیری از حملات بازدرآمد استفاده کند. چون ابتدا باید به وام گیرنده اطلاع دهد که توکن ها منتقل شده اند (فراخوانی خارجی انجام دهد) و سپس توکن ها بازگردانده شوند. بنابراین باید در قرارداد از قفل های nonReentrant استفاده کرد تا از بروز حملات بازدرآمد جلوگیری شود.

بسیار مهم است که قرارداد وام دهنده خودش فرآیند بازگرداندن توکن ها را انجام دهد یا اینکه قفل های جلوگیری از بازدرآمد (reentrancy locks) در قرارداد فعال باشند.

در پیاده سازی هایی که پیش تر توضیح داده شد، قرارداد وام دهنده، توکن ها را از قرارداد وام گیرنده بازپس می گیرد. در این حالت، وام گیرنده مستقیما توکن ها را به قرارداد وام دهنده باز نمی گرداند. این طراحی اهمیت زیادی دارد زیرا از بروز حملاتی مانند “ورودی های جانبی” (side entrances) جلوگیری می کند. در این نوع حمله، وام گیرنده به جای آنکه وام را بازپرداخت کند، همان مقدار را به عنوان سپرده جدید وارد پروتکل می کند و در ظاهر به یک وام دهنده با سپرده بزرگ تبدیل می شود. در نتیجه، موجودی استخر بدون تغییر به نظر می رسد، اما در واقع مهاجم وضعیت خود را در سیستم تغییر داده است.

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

برای وام گیرنده: اطمینان حاصل کنید که تنها قرارداد وام دهنده می تواند تابع onFlashLoan را فراخوانی کند

قرارداد وام دهنده به صورت سخت کدنویسی شده است که فقط می تواند تابع onFlashLoan() را در قرارداد وام گیرنده فراخوانی کند و اجازه اجرای هیچ تابع دیگری را ندارد. اگر وام گیرنده به نحوی می توانست مشخص کند که قرارداد وام دهنده کدام تابع را فراخوانی کند، این امکان وجود داشت که مهاجم، قرارداد وام دهنده را مجبور به انجام انتقال توکن های دیگر (از طریق فراخوانی ERC20.transfer) یا اعطای دسترسی (approval) به یک آدرس مخرب کند.

چون این نوع اقدامات نیاز به فراخوانی مستقیم توابع ERC20 مانند transfer یا approve دارند، چنین حمله ای زمانی رخ نمی دهد که قرارداد وام دهنده تنها قادر به فراخوانی تابع onFlashLoan() باشد.

این نوع حمله در دنیای واقعی نیز رخ داده است. سایت Rekt News یک مورد مستند از هک شدن یک ربات MEV را در این زمینه منتشر کرده است.

استفاده از token.balanceOf(address(this)) می تواند دچار دستکاری شود

در پیاده سازی بالا، از balanceOf(address(this)) فقط برای تعیین حداکثر مقدار قابل وام گیری (maximum flash loan size) استفاده می کنیم. این مقدار می تواند با انتقال مستقیم توکن به قرارداد، توسط اشخاص دیگر تغییر کند و منطق برنامه را دچار اختلال کند.
ما بازپرداخت وام را از این طریق تشخیص نمی دهیم، بلکه قرارداد وام دهنده، مقدار وام + کارمزد را خودش بازمی گرداند. البته می توان از balanceOf(address(this)) به صورت معتبر برای بررسی بازپرداخت استفاده کرد، اما این کار باید همراه با قفل های بازدرآمد (reentrancy checks) انجام شود تا از حمله ای که بازپرداخت را به عنوان سپرده ثبت می کند، جلوگیری شود.

چرا وام گیرنده باید مقدار keccak256(“ERC3156FlashBorrower.onFlashLoan”) را بازگرداند؟

این کار برای جلوگیری از سناریوی خاصی طراحی شده است. فرض کنید یک قرارداد (که قرارداد وام دهنده نیست) دارای یک تابع fallback است و به قرارداد وام دهنده نیز دسترسی (approval) داده است. یک مهاجم می تواند به طور مکرر یک وام فوری با این قرارداد به عنوان گیرنده آغاز کند. در این حالت اتفاقات زیر رخ می دهد:

  1. قرارداد قربانی یک وام فوری دریافت می کند

  2. قرارداد وام دهنده تابع onFlashLoan() را فراخوانی می کند و این باعث اجرای تابع fallback قرارداد قربانی می شود، اما این تابع fallback خطا تولید نمی کند (revert نمی شود). تابع fallback به هر فراخوانی که با توابع دیگر قرارداد مطابقت نداشته باشد، پاسخ می دهد؛ در نتیجه به فراخوانی onFlashLoan() هم پاسخ می دهد.

  3. قرارداد وام دهنده، توکن های وام + کارمزد را از قرارداد قربانی برداشت می کند

اگر این فرآیند در یک حلقه تکرار شود، قرارداد قربانی که دارای fallback است، به تدریج تخلیه می شود. این اتفاق برای یک کیف پول معمولی (EOA wallet) نیز می تواند رخ دهد، زیرا فراخوانی onFlashLoan برای یک آدرس EOA نیز خطا تولید نمی کند.

فقط بررسی اینکه تابع onFlashLoan خطا نمی دهد، کافی نیست. قرارداد وام دهنده باید بررسی کند که مقدار keccak256("ERC3156FlashBorrower.onFlashLoan") بازگردانده شده است. این موضوع به قرارداد وام دهنده اطمینان می دهد که وام گیرنده واقعا قصد داشته توکن ها را وام بگیرد و کارمزد آن را نیز پرداخت کند.

به این مطلب امتیاز دهید

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

دوره آموزش طراحی فروشگاه اینترنتی بدون کد نویسی در 8 ساعت
  • انتشار: ۱۲ خرداد ۱۴۰۴

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

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

مشاهده همه

نظرات

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