حافظ حراج وثیقه
وقتی صندوقی در سامانه استیبلکوین زر ناامن میشود، وثیقهاش به حراج هلندی میرود و هر کسی میتواند آن را بخرد. این راهنما برای تیمهایی نوشته شده است که میخواهند ربات یا قراردادی بسازند که حراجها را پایش کند، نقد شدن صندوقها را آغاز کند و در حراج خرید کند.
پس از خواندن این راهنما میدانید:
- وضعیت هر حراج و پارامترهای هر نوع وثیقه را از کجا بخوانید؛
- چه شرایطی برای آغاز نقد شدن یک صندوق لازم است؛
- چطور از حراج بخرید، با سرمایه خودتان یا بدون آن؛
- خطاهای رایج چه معنایی دارند و چطور برطرفشان کنید.
پیش از شروع، صفحه نقد شدن وثیقه را بخوانید. سازوکار حراج، پارامترهای فعلی و دلیل انتخاب حراج هلندی در آنجا توضیح داده شده است و این راهنما فقط به جنبههای اجرایی میپردازد. نشانی قراردادها را هم در صفحه قراردادهای مستقر آوردهایم.
معماری در یک نگاه
سه قرارداد در حراج وثیقه نقش دارند:
- قرارداد آغاز نقد شدن1 نقطه ورود عمومی برای نقد کردن صندوقهای ناامن است. کل سامانه فقط یک نسخه از این قرارداد دارد.
- قرارداد حراج2 برای هر نوع وثیقه جداگانه مستقر شده است. این قرارداد وثیقه در حال حراج را نگه میدارد، زر خریداران را میپذیرد، وضعیت همه حراجهای آن نوع وثیقه را ذخیره میکند و منطق آغاز، خرید و بازنشانی را در خود دارد.
- منحنی قیمت3 برای هر قرارداد حراج جداگانه مستقر شده و تابعی دارد که از قیمت شروع و زمان سپریشده، قیمت لحظهای حراج را حساب میکند.
نشانی قرارداد حراج هر نوع وثیقه در خود قرارداد آغاز نقد شدن ذخیره شده و نشانی منحنی قیمت هم در قرارداد حراج.
واحدهای عددی
قراردادهای سامانه زر اعداد را بهصورت عدد صحیح با تعداد ثابتی رقم اعشار ذخیره میکنند. پیش از هر مقایسه یا محاسبه، همه مقادیر را به یک واحد برگردانید؛ بیشتر خطاهای یکپارچهسازی از همینجا میآید.
| واحد | رقم اعشار | کاربرد |
|---|---|---|
| وَد4 | ۱۸ | مقدار وثیقه و ضریبهایی مانند جریمه نقد شدن |
| رِی5 | ۲۷ | قیمت و نسبتها |
| رَد6 | ۴۵ | مبلغ زر داخلی، بدهی و هدف حراج |
پایش
پارامترهای هر نوع وثیقه
قرارداد آغاز نقد شدن برای هر نوع وثیقه چهار مقدار نگه میدارد:
struct Ilk {
address clip; // collateral auction contract
uint256 chop; // liquidation penalty [wad]
uint256 hole; // max ZAR to raise in active auctions of this ilk [rad]
uint256 dirt; // ZAR currently being raised for this ilk [rad]
}
mapping (bytes32 => Ilk) public ilks;
کلید این نگاشت نام نوع وثیقه است، مثلاً «اتر الف» که در قرارداد به شکل رشته ۳۲بایتی ذخیره میشود7. سقف بدهی همزمان در حراج و مقدار پرشده آن را پیش از آغاز هر نقد شدن بررسی کنید؛ در بخش «آغاز نقد شدن» توضیح دادهایم چرا. همین دو مقدار در سطح کل سامانه هم وجود دارد8.
وضعیت هر حراج
قرارداد حراج وضعیت هر حراج را در این ساختار ذخیره میکند:
struct Sale {
uint256 pos; // index in the active array
uint256 tab; // ZAR still to raise [rad]
uint256 lot; // collateral still to sell [wad]
address usr; // liquidated vault
uint96 tic; // auction start time
uint256 top; // starting price [ray]
}
mapping (uint256 => Sale) public sales;
کلید این نگاشت شناسه حراج است که قرارداد هنگام آغاز حراج به آن میدهد. شمارنده کل حراجهایی که تا امروز در یک قرارداد حراج شروع شده، در متغیر شمار حراجها9 ذخیره میشود و با هر حراج تازه یکی بالا میرود.
برای خواندن حراجهای زنده دو راه دارید:
- سادهترین راه: تابع فهرست10 آرایه شناسه همه حراجهای فعال را یکجا برمیگرداند. سپس برای هر شناسه، تابع وضعیت11 چهار چیز را برمیگرداند: آیا حراج به بازنشانی نیاز دارد، قیمت لحظهای، وثیقه باقیمانده و هدف باقیمانده.
- محاسبه مستقیم: قیمت شروع و زمان شروع را از ساختار بالا بخوانید، نشانی منحنی قیمت را از قرارداد حراج بگیرید12 و قیمت را خودتان حساب کنید13. ورودی اول قیمت شروع است و ورودی دوم تعداد ثانیههایی که از شروع حراج گذشته است. خروجی قیمت هر واحد وثیقه به زر است و با ۲۷ رقم اعشار برمیگردد.
راه دوم برای حراجهای تمامشده هم کار میکند، اما برای حراج فعال، تابع وضعیت همین محاسبه را همراه با بررسی نیاز به بازنشانی انجام میدهد و خطای کمتری دارد.
یک نکته درباره ترتیب آرایه حراجهای فعال: وقتی حراجی تمام میشود، قرارداد آخرین عنصر آرایه را به جای آن منتقل میکند. پس ترتیب آرایه ترتیب زمانی نیست و آخرین عنصر لزوماً تازهترین حراج نیست. اگر تازهترین حراج را میخواهید، بزرگترین شناسه را پیدا کنید.
رویدادها
قرارداد حراج سه رویداد منتشر میکند که برای ساختن تاریخچه کافی است:
- رویداد آغاز14 وقتی منتشر میشود که حراجی شروع شود. قیمت شروع، هدف حراج، مقدار وثیقه، صندوق نقدشده و پاداش پرداختشده در آن ثبت است.
- رویداد خرید15 با هر خرید منتشر میشود. چون خرید بخشی مجاز است، یک حراج میتواند چند رویداد خرید داشته باشد و یک صندوق هم میتواند در چند حراج نقد شده باشد.
- رویداد بازنشانی16 وقتی منتشر میشود که حراجی با قیمت تازه دوباره شروع شود.
آغاز نقد شدن
هر کسی میتواند تابع آغاز نقد شدن17 را با سه ورودی فراخوانی کند: نام نوع وثیقه، نشانی صندوق و نشانیای که پاداش آغاز حراج را دریافت میکند. قرارداد در این شرایط تراکنش را رد میکند:
- صندوق امن است. صندوق وقتی نقدشدنی است که حاصلضرب مقدار وثیقه در قیمت ایمن از حاصلضرب بدهی نرمالشده در نرخ انباشته کمتر باشد18. قیمت ایمن، قیمت اوراکل تقسیم بر نسبت نقد شدن است و قرارداد اسپات آن را در قرارداد مدیریت صندوقها ثبت میکند.
- جایی در سقف حراج نمانده است. مقدار پرشده سقف، هم برای کل سامانه و هم برای آن نوع وثیقه، باید کمتر از سقف باشد. اگر یکی از این دو پر شده باشد، نقد شدن تا وقتی حراجهای جاری زر جمع کنند و جا باز شود، متوقف میماند.
- بخش نقدشده یا باقیمانده بیش از حد کوچک است. اگر فضای خالی سقف فقط برای بخشی از بدهی کافی باشد، قرارداد همان بخش را نقد میکند. اما اگر بدهی باقیمانده در صندوق از حداقل بدهی19 کمتر شود، کل صندوق را نقد میکند؛ و اگر خود بخش نقدشده از حداقل بدهی کمتر باشد، تراکنش را رد میکند.
پیش از ارسال تراکنش، همین شرایط را خودتان بررسی کنید یا تراکنش را بدون ارسال شبیهسازی کنید20 تا کارمزد تراکنش ناموفق را نپردازید.
برخلاف طراحیهای قدیمیتر، سقفی برای مقدار نقد شدن هر صندوق در یک نوبت وجود ندارد و فقط سقف بدهی همزمان در حراج اندازه نقد شدن را محدود میکند. برای مثال، اگر این سقف برای یک نوع وثیقه ۳۰٬۰۰۰٬۰۰۰ زر و جریمه نقد شدن ۱۳ درصد باشد و هیچ حراج دیگری در جریان نباشد، صندوقی با بدهی تا حدود ۲۶٬۵۴۹٬۰۰۰ زر در یک حراج با هدف ۳۰٬۰۰۰٬۰۰۰ زر نقد میشود. صندوق بزرگتر از این، بخشبخش نقد میشود.
خرید از حراج
در حراج هلندی مفهوم پیشنهاد عملاً وجود ندارد. هر کسی که به حراج فعالی زر بفرستد، در همان تراکنش به نسبت قیمت لحظهای وثیقه دریافت میکند. بهتر است هر حراج را یک سفارش فروش بزرگ در دفتر سفارش یک صرافی تصور کنید. فرض کنید حراجی ۱۰ اتر را به قیمت ۳۰۰٬۰۰۰ زر برای هر اتر میفروشد؛ هر کسی با حداکثر ۳٬۰۰۰٬۰۰۰ زر میتواند به همین قیمت اتر بخرد و چند خریدار میتوانند بخشبخش این سفارش را پر کنند تا وثیقه تمام شود یا هدف حراج پوشش داده شود.
آمادهسازی
قرارداد حراج با موجودی داخلی زر در قرارداد مدیریت صندوقها کار میکند، نه با توکن زر. پس پیش از اولین خرید، سه کار لازم است:
- اجازه برداشت توکن: به قرارداد الحاق زر ا جازه دهید مقداری از توکن زر شما را بردارد21.
- تبدیل توکن به موجودی داخلی: زر را از راه قرارداد الحاق زر وارد سامانه حسابداری کنید22.
- اجازه به قرارداد حراج: در قرارداد مدیریت صندوقها به قرارداد حراج همان نوع وثیقه اجازه دهید زر داخلی را از حسابتان بردارد23.
این سه کار سه تراکنش جداست. اگر از قرارداد پراکسی24 استفاده کنید، میتوانید آنها را در یک تراکنش انجام دهید.
فراخوانی خرید
function take(
uint256 id, // auction id
uint256 amt, // upper limit on collateral to buy [wad]
uint256 max, // maximum acceptable price, ZAR/unit [ray]
address who, // receiver of collateral, and callee address
bytes calldata data // callee data; empty for a plain purchase
) external;
قرارداد با این فراخوانی چند کار انجام میدهد:
- بررسی میکند حراج فعال است و به بازنشانی نیاز ندارد.
- قیمت لحظهای را حساب میکند و اگر از حداکثر قیمت شما بیشتر باشد، تراکنش را رد میکند.
- کمتر از دو مقدار را میفروشد: مقدار درخواستی شما یا مقداری که هدف باقیمانده حراج را پوشش میدهد. پس اگر بیشتر از نیاز حراج درخواست کنید، فقط به اندازه نیاز زر میپردازید.
- مانع میشود که خرید بخشی، حراجی بسیار کوچک به جا بگذارد. اگر پس از خرید شما هدف باقیمانده از حداقل مجاز25 کمتر شود، قرارداد مقدار خرید را آنقدر کم میکند که دقیقاً همین حداقل باقی بماند. اگر هدف باقیمانده از قبل به این حداقل رسیده باشد، فقط خرید کل وثیقه ممکن است.
- وثیقه را به موجودی داخلی نشانی دریافتکننده منتقل میکند و زر را از حساب فراخواننده به ترازنامه سامانه میفرستد.
- اگر هدف حراج کامل پوشش داده شود، حراج را میبندد و وثیقه فروختهنشده را به صندوق برمیگرداند.
حداکثر قیمت را با دقت تعیین کنید. قیمت حراج با زمان فقط پایین میآید، اما اگر حراج پیش از تأیید تراکنش شما بازنشانی شود، قیمت دوباره از قیمت اوراکل شروع میشود. همین پارامتر جلوی خرید به قیمتی بالاتر از انتظارتان را میگیرد.
دریافت وثیقه
وثیقه خریداریشده ابتدا بهصورت موجودی داخلی در قرارداد مدیریت صندوقها ثبت میشود. برای تبدیلش به توکن، از قرارداد الحاق همان نوع وثیقه برداشت کنید26. قرارداد الحاق داراییهایی که کمتر از ۱۸ رقم اعشار دا رند، مانند رپد بیتکوین با ۸ رقم، مقدار ورودی را به تعداد اعشار خود توکن میگیرد؛ پس مقدار داخلی را پیش از برداشت تبدیل کنید.
نمونه قرارداد
قرارداد زیر از تازهترین حراج فعال یک نوع وثیقه خرید میکند، به شرط آنکه قیمت از سقف تعیینشده بالاتر نباشد. فرض بر این است که قرارداد از پیش موجودی داخلی زر دارد و به قرارداد حراج اجازه برداشت داده است. وثیقه خریداریشده در موجودی داخلی خود قرارداد میماند و برای برداشتش به تابعی جداگانه نیاز دارید که فقط مالک بتواند فراخوانی کند.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface ClipperLike {
function list() external view returns (uint256[] memory);
function getStatus(uint256 id)
external
view
returns (bool needsRedo, uint256 price, uint256 lot, uint256 tab);
function take(uint256 id, uint256 amt, uint256 max, address who, bytes calldata data) external;
}
contract AuctionBuyer {
address public immutable owner;
constructor() {
owner = msg.sender;
}
function takeFromNewest(ClipperLike clip, uint256 amt, uint256 maxPrice) external {
require(msg.sender == owner, "AuctionBuyer/not-owner");
uint256[] memory ids = clip.list();
require(ids.length > 0, "AuctionBuyer/no-active-auction");
uint256 newest = ids[0];
for (uint256 i = 1; i < ids.length; i++) {
if (ids[i] > newest) newest = ids[i];
}
(bool needsRedo, uint256 price,,) = clip.getStatus(newest);
require(!needsRedo, "AuctionBuyer/needs-redo");
require(price <= maxPrice, "AuctionBuyer/too-expensive");
clip.take(newest, amt, maxPrice, address(this), "");
}
}
این قرارداد نمونهای آموزشی است و پیش از استفاده روی شبکه اصلی باید آزموده و بازبینی شود.
خرید بدون سرمایه اولیه
قرارداد حراج وثیقه را پیش از دریافت زر تحویل میدهد. اگر نشانی دریافتکننده یک قرارداد باشد و داده فراخوانی خالی نباشد، قرارداد حراج پس از انتقال وثیقه، این تابع را روی قرارداد دریافتکننده صدا میزند27:
interface ClipperCallee {
function clipperCall(
address sender, // caller of take; pays the ZAR
uint256 owe, // ZAR to pay [rad]
uint256 slice, // collateral received [wad]
bytes calldata data // your own encoded parameters
) external;
}
قرارداد دریافتکننده در این تابع معمولاً این کارها را انجام میدهد:
- وثیقه را از قرارداد الحاق وثیقه به شکل توکن برداشت میکند.
- توکن را در یک صرافی غیرمتمرکز، مثلاً یونیسواپ نسخه ۳، به زر تبدیل میکند و برای خروجی معامله کفی برابر مبلغ بدهی بهاضافه سود مورد انتظار تعیین میکند.
- زر لازم را از راه قرارداد الحاق زر به موجودی داخلی فراخواننده اصلی وارد میکند.
- سود باقیمانده را به نشانی دلخواه منتقل میکند.
قرارداد حراج برای جلوگیری از سوءاستفاده، قرارداد مدیریت صندوقها و قرارداد آغاز نقد شدن را بهعنوان دریافتکننده فراخوانی نمیکند. قفل ضدبازگشتی28 هم مانع میشود که قرارداد دریافتکننده در میانه همین تابع دوباره از حراج بخرد یا آن را بازنشانی کند.
پس از بازگشت این تابع، قرارداد حراج زر را از حساب فراخواننده برمیدارد. اگر معامله در صرافی به کف تعیینشده نرسد، کل تراکنش برمیگردد و خریدار چیزی جز کارمزد تراکنش از دست نمیدهد. فراخواننده همچنان باید به قرارداد حراج اجازه برداشت زر داخلی داده باشد، اما لازم نیست پیش از خرید موجودی زر داشته باشد.
بازنشانی حراج
اگر زمان مجاز حراج تمام شود یا قیمت به کمتر از کف تعیینشده نسبت به قیمت شروع برسد، قرارداد دیگر خریدی را نمیپذیرد و تابع وضعیت نیاز به بازنشانی را اعلام میکند. در این حالت هر کسی میتواند تابع بازنشانی29 را با شناسه حراج و نشانی دریافت پاداش فراخوانی کند. اگر حراج هنوز به بازنشانی نیاز نداشته باشد، قرارداد تراکنش را رد میکند. در غیر این صورت قیمت شروع تازه را از قیمت فعلی اوراکل میگیرد، زمانسنج را از صفر شروع میکند و همان پاداش آغاز حراج را به فراخواننده میپردازد. قرارداد این پاداش را فقط وقتی میپردازد که هم هدف باقیمانده و هم ارز ش وثیقه باقیمانده دستکم برابر حداقل مجاز باشد؛ به این ترتیب بازنشانی مکرر حراجهای بسیار کوچک برای گرفتن پاداش صرف نمیکند.
حافظی که حراجها را پایش میکند، بهتر است بازنشانی را هم بر عهده بگیرد؛ هم پاداش دارد و هم حراج را دوباره قابل خرید میکند.
توقف و لغو حراج
حاکمیت پروتکل برای شرایط اضطراری یک سازوکار توقف سهسطحی در اختیار دارد30:
| سطح | چه چیزی متوقف میشود |
|---|---|
| ۰ | هیچ مرحلهای متوقف نیست. |
| ۱ | قرارداد حراج تازهای آغاز نمیکند. |
| ۲ | قرارداد حراج تازهای آغاز نمیکند و حراجها را بازنشانی نمیکند. |
| ۳ | قرارداد حراج تازهای آغاز نمیکند، حراجها را بازنشانی نمیکند و خریدی نمیپذیرد. |
افزون بر این، حاکمیت یا فرایند خاموشی اضطراری سامانه میتواند یک حراج فعال را لغو کند31. در این حالت قرارداد حراج را از فهرست فعالها حذف میکند، مبلغ آن را از سقف حراج آزاد میکند و وثیقه باقیمانده را به فراخواننده لغو تحویل میدهد. ربات حافظ باید حذف ناگهانی حراج از فهرست را، بیآنکه رویداد خریدی دیده باشد، بهعنوان یک حالت عادی مدیریت کند.
خطاهای رایج
| خطا | علت | راهحل |
|---|---|---|
| صندوق ناامن نیست32 | صندوق هنوز بالای آستانه است یا قیمت تازه اوراکل هنوز اعمال نشده است. | پیش از فراخوانی، ناامن بودن صندوق را با همان قیمتی بسنجید که در قرارداد مدیریت صندوقها ثبت شده است. |
| سقف حراج پر شده است33 | حراجهای جاری کل سقف سامانه یا سقف آن نوع وثیقه را پر کردهاند. | صبر کنید تا خریدها بخشی از سقف را آزاد کنند. |
| حراج بخشی بیش از حد کوچک است34 | فضای خالی سقف فقط برای بخشی کوچکتر از حداقل بدهی کافی است. | صبر کنید تا فضای خالی سقف بیشتر شود. |
| حراج به بازنشانی نیاز دارد35 | زمان حراج تمام شده یا قیمت از کف گذشته است. | ابتدا حراج را بازنشانی کنید و قیمت تازه را بخوانید. |
| قیمت از سقف شما بالاتر است36 | حداکثر قیمت کمتر از قیمت لحظهای است یا حراج در این فاصله بازنشانی شده است. | قیمت را دوباره بخوانید و حداکثر قیمت را با واحد درست تعیین کنید. |
| حراج فعال نیست37 | حراج پیش از رسیدن تراکنش شما کامل فروخته شده است. | فهرست حراجهای فعال را دوباره بخوانید. |
| خرید بخشی مجاز نیست38 | پس از خرید شما، هدف باقیمانده حراج کوچکتر از حداقل مجاز میشد و خود حراج هم از این حداقل بزرگتر نیست. | کل حراج را بخرید. |
| قرارداد حراج متوقف است39 | حاکمیت پروتکل این مرحله از حراج را موقتاً متوقف کرده است. | سطح توقف را بخوانید و تا رفع آن صبر کنید. |
| حراج به بازنشانی نیاز ندارد40 | هنوز نه زمان حراج تمام شده و نه قیمت از کف گذشته است. | پیش از بازنشانی، خروجی تابع وضعیت را بررسی کنید. |
| قیمت اوراکل معتبر نیست41 | ماژول امنیتی اوراکل در این لحظه قیمت معتبری ندارد و قرارداد نمیتواند قیمت شروع را تعیین کند. | صبر کنید تا اوراکل بهروز شود و دوباره تلاش کنید. |
| سامانه قفل است42 | قرارداد دریافتکننده در میانه خرید، دوباره قرارداد حراج را فراخوانی کرده است. | در قرارداد دریافتکننده، هیچ فراخوانی به همان قرارداد حراج انجام ندهید. |
در همه این موارد، خطای تبدیل واحد را هم در نظر بگیرید. حداکثر قیمتی که به جای ۲۷ رقم با ۱۸ رقم اعشار ارسال شود، عملاً صفر است و هر خریدی را رد میکند.
مطالب مرتبط
این راهنما بر پایه راهنمای یکپارچهسازی حراج وثیقه میکردائو43 نوشته شده و برای قراردادها، واحد پول و پارامترهای زربان بازنویسی و تکمیل شده است.