آموزش توابع Mint و Burn در Uniswap V2

چرخه کاری Uniswap V2 به این صورت است که ابتدا یک کاربر برای اولین بار توکن های LP (مخفف Liquidity Provider Token یا توکن های ارائه دهنده نقدینگی) را تولید می کند. به این معنا که مقداری از توکن ها را به استخر نقدینگی واریز می کند. سپس کاربران دیگری نیز توکن های نقدینگی جدید تولید می کنند، در ادامه عملیات سواپ (مبادله توکن ها) انجام می شود و در نهایت، ارائه دهندگان نقدینگی توکن های LP خود را می سوزانند (Burn) تا توکن های موجود در استخر را مجددا دریافت کنند. در این میان، تابع Mint و Burn در Uniswap V2 نقش کلیدی در مدیریت این فرآیند ایفا می کنند و نحوه تولید و بازخرید توکن های LP را مشخص می سازند.

بررسی این توابع به صورت معکوس ساده تر است؛ یعنی ابتدا فرآیند سوزاندن (Burn)، سپس تولید نقدینگی (Mint Liquidity) و در نهایت تولید نقدینگی اولیه (Initial Minting).

در نتیجه، ابتدا با بررسی تابع Burn شروع می کنیم.

تابع Burn در Uniswap V2

قبل از آن که بتوان توکن های نقدینگی (Liquidity Tokens) را سوزاند، باید در استخر نقدینگی وجود داشته باشد. بنابراین فرض می کنیم که هم اکنون مقداری نقدینگی در استخر قرار دارد. همچنین فرض می کنیم که دو نوع توکن در این سیستم وجود دارد: token0 و token1.

در ادامه، تابع Burn را همراه با توضیحات بخش های غیر واضح آن بررسی می کنیم.

تابع burn در Uniswap V2

آماده سازی برای فراخوانی تابع Burn

در خط ۱۴۰ (کادر بنفش)، قرارداد استخر با استفاده از تعداد توکن های LP موجود، میزان نقدینگی را اندازه گیری می کند. کاربری که قصد سوزاندن توکن های خود را دارد، ابتدا باید این توکن ها را به قرارداد انتقال دهد. بهترین روش آن است که این انتقال و اجرای تابع burn را در قالب یک تراکنش انجام دهند. در غیر این صورت، اگر انتقال و فراخوانی تابع در دو تراکنش جدا انجام شود، امکان دارد شخص دیگری توکن های LP را بسوزاند و نقدینگی را برداشت کند.

در این فرآیند، همان مقدار توکن LP که کاربر به قرارداد ارسال می کند، سوزانده می شود. معمولاً قرارداد باید هیچ توکن LP در موجودی خود نگه ندارد. وجود این توکن ها در قرارداد جفت (pair contract) باعث می شود دیگران بتوانند آن ها را سوزانده و توکن های token0 و token1 را به صورت رایگان دریافت کنند. در مقاله مربوط به تابع Swap در Uniswap V2، نحوه ارسال توکن ها همراه با تراکنش توضیح داده شده است.

روند محاسبه و به روز رسانی در تابع Burn

در خطوط ۱۴۲ و ۱۵۴ (کادر قرمز)، بخش مربوط به کارمزدها قرار دارد. این قسمت فعلاً بررسی نمی شود، زیرا Uniswap در این مرحله از فرآیند برای ارائه دهندگان نقدینگی کارمزدی دریافت نمی کند.

در خطوط ۱۴۴ تا ۱۴۵ (کادر نارنجی)، سیستم محاسبه می کند که کاربر چه میزان از توکن های token0 و token1 را دریافت خواهد کرد. فرض کنید کل عرضه توکن های LP برابر با ۱۰۰۰ باشد. اگر کاربر ۱۰۰ توکن LP بسوزاند، معادل ۱۰ درصد از توکن های موجود در استخر به او تعلق می گیرد. نسبت Liquidity / totalSupply میزان سهم کاربر از کل عرضه را نشان می دهد.

در خطوط ۱۴۷ تا ۱۴۹ (کادر آبی)، قرارداد توکن های LP را می سوزاند و سپس توکن های token0 و token1 را به کیف پول کاربر ارسال می کند.

در خطوط ۱۵۰ تا ۱۵۱ (کادر زرد)، متغیرهای موجودی به روز رسانی می شوند. این به روز رسانی زمینه را برای اجرای تابع _update در خط ۱۵۳ (کادر سبز) فراهم می کند. تابع _update، علاوه بر بروزرسانی TWAP، متغیرهای _reserve را نیز به روز می کند.

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

بررسی های ایمنی

فرض کنید استخر دارای مقادیر معادل از توکن های token0 و token1 است. در چنین شرایطی، کاربری که توکن LP خود را می سوزاند، انتظار دارد که هر دو توکن را به میزان برابر دریافت کند. با این حال، نسبت توکن های token0 به token1 ممکن است بین زمان امضای تراکنش burn و زمان تایید آن تغییر کند. اگر کاربر در منطق پایین دست خود (downstream logic)، به دریافت مقدار مشخصی از token0 یا token1 وابسته باشد (برای مثال جهت بازپرداخت یک وام فوری در سالیدیتی)، این منطق در صورت دریافت مقدار کمتر از حد انتظار، دچار مشکل خواهد شد. بنابراین، قرارداد مسئول فرآیند burn باید آمادگی داشته باشد که در صورت دریافت کمتر از مقدار مورد انتظار از token0 یا token1، عملیات را متوقف کند (revert) تا از بروز خطا در منطق بعدی جلوگیری شود.

افزودن نقدینگی زمانی که استخر خالی نیست

در این بخش به تابع mint liquidity می پردازیم. بخش زیادی از عملکرد این تابع مشابه تابع burn است، بنابراین قسمت های واضح را تکرار نمی کنیم.

تابع mint در uniswap v2

اگر استخر خالی باشد، یعنی کل عرضه توکن های نقدینگی صفر باشد، در این صورت هنوز هیچ نقدینگی به استخر اضافه نشده است. بررسی این حالت در خط ۱۱۹ (کادر زرد) انجام می شود. اما در اینجا تمرکز ما روی حالتی است که قبلاً مقداری نقدینگی به استخر اضافه شده است (کادر زرد در خط ۱۲۳). میزان نقدینگی که به حساب کاربر اضافه می شود و در نهایت در خط ۱۲۶ (کادر سبز) برای او تولید (mint) می شود، برابر با مقدار کمتر از دو مقدار محاسبه شده خواهد بود.

نحوه محاسبه نقدینگی

نسبتی که در این بخش از کد محاسبه می شود amount0 / _reserve0 است که با totalSupply توکن های LP مقیاس دهی می شود.

به عنوان مثال، فرض کنید در استخر ۱۰ توکن token0 و ۱۰ توکن token1 وجود داشته باشد. اگر کاربر ۱۰ توکن token0 و صفر توکن token1 وارد کند، در این صورت مقدار مینیمم از نسبت ها (۱۰/۱۰ ، ۰/۱۰) محاسبه می شود و کاربر هیچ توکن نقدینگی (LP Token) دریافت نخواهد کرد!

یک مثال دیگر: اگر کاربر به جای آن مقدار مناسبی از هر دو توکن وارد کند، می تواند به عرضه توکن های LP بیفزاید. در اینجا باید به این نکته توجه کرد که این نسبت بر اساس _totalSupply فعلی توکن های LP مقیاس داده می شود.

این نکته که کاربر بدترین نسبت از دو نسبت (amount0 / _reserve0 یا amount1 / _reserve1) را دریافت می کند، او را تشویق می کند که هر دو توکن token0 و token1 را به طور متناسب افزایش دهد و نسبت بین آن ها را تغییر ندهد. این سازوکار باعث می شود تعادل استخر حفظ شود و نسبت توکن ها ثابت بماند.

چرا باید این محدودیت اعمال شود؟

فرض کنید استخر در حال حاضر ۱۰۰ واحد token0 و ۱ واحد token1 دارد. همچنین مجموع عرضه توکن های LP برابر با ۱ واحد است. ارزش دلاری هر دو توکن را نیز ۱۰۰ دلار در نظر بگیریم. در این شرایط، مجموع ارزش استخر برابر با ۲۰۰ دلار خواهد بود.

حالا تصور کنید در محاسبه به جای کمترین نسبت، بیشترین نسبت را لحاظ کنیم. در این حالت، کاربر می تواند تنها با افزودن ۱ واحد token1 (به ارزش ۱۰۰ دلار) ارزش کل استخر را به ۳۰۰ دلار برساند. به این ترتیب، او ۵۰ درصد ارزش استخر را افزایش داده است.

اما استفاده از نسبت بیشینه یک مشکل جدی ایجاد می کند. در این روش، کاربر ۱ توکن LP جدید دریافت می کند. بنابراین، مجموع عرضه توکن های LP به ۲ واحد می رسد. اکنون این کاربر ۵۰ درصد از توکن های LP را در اختیار دارد. یعنی با واریز تنها ۱۰۰ دلار، کنترل ۵۰ درصد از استخر جدید (به ارزش ۱۵۰ دلار) را به دست می آورد.

این روند عملاً به ضرر سایر ارائه دهندگان نقدینگی تمام می شود. چرا که این کاربر بدون تأمین ارزش متعادل برای استخر، سهم قابل توجهی کسب می کند. این فرآیند نوعی سوء استفاده محسوب می شود.

برای جلوگیری از چنین سوء استفاده هایی، Uniswap همواره از کمترین نسبت بین amount0 / _reserve0 و amount1 / _reserve1 استفاده می کند. این کار باعث می شود تعادل استخر حفظ شود و سهم کاربران به شکل عادلانه محاسبه گردد.

بررسی ایمنی نسبت عرضه 

ممکن است کاربر هنگام افزودن نقدینگی، سعی کند نسبت بین توکن ها را به درستی رعایت کند. اما اگر پیش از ثبت تراکنش او، یک تراکنش دیگر اجرا شود و تعادل بین token0 و token1 را تغییر دهد، کاربر در نهایت توکن های نقدینگی (LP Tokens) کمتری نسبت به آنچه انتظار داشت، دریافت می کند.

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

بررسی ایمنی totalSupply

مشابه فرآیند burn، هنگام افزودن نقدینگی نیز totalSupply توکن های LP می تواند در همان زمان تغییر کند. بنابراین لازم است مکانیزم محافظت در برابر لغزش قیمت (slippage protection) پیاده سازی شود. این کار از بروز اختلافات و دریافت کمتر از مقدار مورد انتظار جلوگیری می کند.

مشکل اولین ارائه دهنده نقدینگی (The First Minter Problem)

مانند هر استخر نقدینگی (LP Pool)، Uniswap V2 نیز باید در برابر “حمله تورمی” (inflation attack) مقاوم باشد. ما پیشتر این مشکل و روش های مقابله با آن را در مقاله مربوط به استاندارد ERC4626 در سالیدیتی توضیح داده ایم، بنابراین در اینجا به جزئیات آن نمی پردازیم.

در Uniswap V2، برای مقابله با این حمله، مقدار MINIMUM_LIQUIDITY از توکن های LP ابتدا سوزانده (burn) می شود. این کار تضمین می کند که هیچ کاربری به تنهایی مالک کل عرضه توکن های LP نباشد و نتواند به سادگی قیمت ها را دستکاری کند. برای جزئیات بیشتر درباره این نوع حمله، لطفاً به مقاله یاد شده مراجعه کنید.

حداقل نقدینگی

چرا Uniswap میزان نقدینگی را به صورت ریشه دوم K محاسبه می کند؟

یک سوال جالب این است که چرا Uniswap V2 برای محاسبه تعداد توکن های LP که باید تولید شود (mint)، از ریشه دوم حاصلضرب مقادیر توکن های واریز شده استفاده می کند.

نقدینگی به عنوان ریشه دوم k

به طور دقیق تر، میزان نقدینگی به این صورت محاسبه می شود:

liquidity = sqrt(amount0 * amount1) (پس از کسر مقدار MINIMUM_LIQUIDITY)

ممکن است این سوال پیش بیاید که چرا اصلاً به این صورت محاسبه می شود؟ در نگاه اول، به نظر می رسد که برای اولین ارائه دهنده نقدینگی (first LP)، می توان به طور دلخواه هر تعداد توکن LP صادر کرد. این کاربر به هر حال مالک ۱۰۰ درصد سهم استخر (منهای مقدار سوزانده شده) خواهد بود. پس چه تفاوتی ایجاد می کند که این مقیاس با ۰.۰۱ ضرب شود یا با ۱۰۰؟

در اینجا توجیهی که وایت پیپر Uniswap ارائه می دهد آمده است:

در نسخه دوم Uniswap، در ابتدا تعداد توکن های سهم (shares) به اندازه میانگین هندسی مقادیر توکن های واریز شده تولید می شود: liquidity = sqrt(x * y). این فرمول باعث می شود ارزش هر سهم از استخر نقدینگی، در هر زمان، تقریباً مستقل از نسبت اولیه واریز نقدینگی باشد… این فرمول تضمین می کند که ارزش هر سهم از استخر نقدینگی، هرگز کمتر از میانگین هندسی ذخایر آن استخر نخواهد شد.

اما این دقیقاً به چه معنا است؟

یکی از بهترین روش ها برای درک این مفهوم این است که مقادیر مختلف را در این فرمول جایگذاری کنیم و ببینیم چه اتفاقی می افتد. در ادامه این بررسی را انجام خواهیم داد.

مثال: دو برابر شدن نقدینگی

فرض کنید اگر Uniswap برای محاسبه نقدینگی از ریشه دوم استفاده نمی کرد. در این حالت، استخر ابتدا شامل ۱۰ واحد token0 و ۱۰ واحد token1 است. بعد از مدتی، این مقادیر به ۲۰ واحد token0 و ۲۰ واحد token1 افزایش پیدا می کنند.

سوال اینجاست: آیا نقدینگی واقعاً دو برابر شده یا چهار برابر؟
اگر از ریشه دوم استفاده نکنیم، مقدار نقدینگی در ابتدا ۱۰۰ می شود (۱۰ × ۱۰) و در انتها به ۴۰۰ می رسد (۲۰ × ۲۰). اما به طور منطقی نمی توان گفت که نقدینگی چهار برابر شده است. در ابتدا، حداکثر مقدار token0 که می توانستید دریافت کنید (به طور تقریبی) ۱۰۰ بود. پس از افزایش نقدینگی، عمق نقدینگی برای آن توکن دو برابر شده است، نه چهار برابر.

اما شاید این سوال پیش بیاید که اگر ارائه دهندگان نقدینگی (LPs) در آینده هنگام mint یا burn کردن، نقدینگی را با ریشه دوم محاسبه نمی کنند، این موضوع چه اهمیتی دارد؟
قبلاً دیدیم که ارائه دهندگان جدید نقدینگی مجبورند دارایی ها را با نسبت فعلی وارد کنند. همچنین، کاربران هنگام سوزاندن توکن های LP فقط می توانند بر اساس نسبت فعلی توکن ها برداشت انجام دهند. در این روند، عملاً ریشه دوم وارد محاسبات مستقیم آن ها نمی شود.

پس چرا این موضوع اهمیت دارد؟ پاسخ در نحوه محاسبه کارمزدها توسط Uniswap نهفته است. اگر Uniswap تصمیم بگیرد از ارائه دهندگان نقدینگی کارمزد بگیرد، نحوه محاسبه نقدینگی با ریشه دوم نقشی کلیدی در این محاسبه ایفا خواهد کرد.

کارمزدها (Fees)

بیایید دوباره به مثال قبلی برگردیم. در این مثال، استخر از ۱۰۰ واحد token0 و ۱۰۰ واحد token1 به ۲۰۰ واحد از هر دو توکن افزایش یافته است. در این وضعیت، سود ارائه دهنده نقدینگی ۱۰۰ درصد است. بنابراین منطقی است که کارمزدی متناسب با این میزان سود پرداخت شود.

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

Uniswap تصمیم گرفته است که کارمزدها را در زمان برداشت نقدینگی (liquidity removal) محاسبه کند. اگر قرار بود این کارمزدها هنگام مبادله توکن ها (swapping) دریافت شوند، هزینه گس (gas cost) برای این عملیات پرکاربرد به طور قابل توجهی افزایش می یافت.

البته باید توجه داشت که در Uniswap V2، کارمزد پروتکل (protocol fee) هیچگاه به طور رسمی فعال نشد. بنابراین این بحث بیشتر جنبه تئوریک دارد.

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

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

پکیج صفر تا صد آموزش سئو و بهینه سازی بصورت عملی
  • انتشار: ۱۹ خرداد ۱۴۰۴

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

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

مشاهده همه

نظرات

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