Q1ngying

今朝梦醒与君别,遥盼春风寄相思

0%

MakerDAO-Clip

MakerDAO Clip 合约分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
// SPDX-License-Identifier: AGPL-3.0-or-later

/// clip.sol -- Dai auction module 2.0

// Copyright (C) 2020-2022 Dai Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.6.12;

/// @notice 金库合约接口
interface VatLike {
/// @notice 用户之间转移稳定币
function move(address, address, uint256) external;
/// @notice 用户之间转移抵押品
function flux(bytes32, address, address, uint256) external;
/// @notice 返回资产的相关参数
function ilks(bytes32) external returns (uint256, uint256, uint256, uint256, uint256);
/// @notice 增加系统中的债务和不良债务,同时向特定用户增加 Dai
function suck(address, address, uint256) external;
}

/// @notice 价格预言机接口
interface PipLike {
function peek() external returns (bytes32, bool);
}

/// @notice 现货合约接口
interface SpotterLike {
function par() external returns (uint256);
function ilks(bytes32) external returns (PipLike, uint256);
}

/// @notice 清算合约接口
interface DogLike {
function chop(bytes32) external returns (uint256);
function digs(bytes32, uint256) external;
}

/// @notice 闪电贷合约接口
interface ClipperCallee {
function clipperCall(address, uint256, uint256, bytes calldata) external;
}

/// @notice Abacus 计算合约接口
interface AbacusLike {
function price(uint256, uint256) external view returns (uint256);
}

/**
* @title
* @author
* @notice 拍卖合约
*/
contract Clipper {
// --- Auth --- 权限控制部分
mapping(address => uint256) public wards;

function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

modifier auth() {
require(wards[msg.sender] == 1, "Clipper/not-authorized");
_;
}

// --- Data ---
bytes32 public immutable ilk; // Collateral type of this Clipper 该拍卖合约的抵押品类型
VatLike public immutable vat; // Core CDP Engine 核心 CDP 引擎 (Vat)

DogLike public dog; // Liquidation module 清算模块
address public vow; // Recipient of dai raised in auctions 拍卖中筹集的 DAI 的接收者
SpotterLike public spotter; // Collateral price module 抵押品价格模块
AbacusLike public calc; // Current price calculator 当前价格计算器

/// @dev `buf`:增加起始价格的乘数 [ray]
uint256 public buf; // Multiplicative factor to increase starting price [ray]
/// @dev `tail`:拍卖重置前经历的时间 [seconds]
uint256 public tail; // Time elapsed before auction reset [seconds]
/// @dev `cusp`:拍卖重置前下降的百分比 [ray]
uint256 public cusp; // Percentage drop before auction reset [ray]
/// @dev `chip`:从 `vow` 中取出 `tab` 的百分比,用来激励 Keepper [wad]
uint64 public chip; // Percentage of tab to suck from vow to incentivize keepers [wad]
/// @dev `tip`:从 `vow` 中取出的固定费用,用来激励 keeppers [rad]
uint192 public tip; // Flat fee to suck from vow to incentivize keepers [rad]
/// @dev `chost`:缓存 `ilk dust` 乘以 `ilk chop`以防止过多的 SLOAD [rad]
uint256 public chost; // Cache the ilk dust times the ilk chop to prevent excessive SLOADs [rad]

/// @dev 总拍卖(包括已经结束的,不在活跃的拍卖)
/// @notice 该值只增不减(类似于 nonce)
uint256 public kicks; // Total auctions
/// @dev 活跃拍卖 ID 数组,其中存储目前正在活跃的拍卖的 id
uint256[] public active; // Array of active auction ids

struct Sale {
uint256 pos; // Index in active array 活跃数组序号
uint256 tab; // Dai to raise [rad] 要筹集的 Dai [rad]
uint256 lot; // collateral to sell [wad] 出售的抵押品数量 [wad]
address usr; // Liquidated CDP 清算的 CDP
uint96 tic; // Auction start time 拍卖开始时间
uint256 top; // Starting price [ray] 起始价格 [ray]
}

/// @notice sale 映射
/// @notice key:活跃拍卖 id(是活跃拍卖 id,不是活跃拍卖 id 数组的 index)
/// @notice value:Sale 结构体
/// @notice 当拍卖不再活跃时,通过 `_remove()` 函数 `delete` 对应条目的数据
mapping(uint256 => Sale) public sales;

uint256 internal locked;

// Levels for circuit breaker 断路器的四个级别
// 0: no breaker
// 1: no new kick()
// 2: no new kick() or redo()
// 3: no new kick(), redo(), or take()
uint256 public stopped = 0;

// --- Events ---
event Rely(address indexed usr);
event Deny(address indexed usr);

event File(bytes32 indexed what, uint256 data);
event File(bytes32 indexed what, address data);

/// @notice 拍卖启动时触发该事件
event Kick(
uint256 indexed id,
uint256 top,
uint256 tab,
uint256 lot,
address indexed usr,
address indexed kpr,
uint256 coin
);

/// @notice 用户参与拍卖时,触发该事件
event Take(
uint256 indexed id, uint256 max, uint256 price, uint256 owe, uint256 tab, uint256 lot, address indexed usr
);

/// @notice 拍卖重启时触发该事件
event Redo(
uint256 indexed id,
uint256 top,
uint256 tab,
uint256 lot,
address indexed usr,
address indexed kpr,
uint256 coin
);

/// @notice 紧急关闭拍卖时触发该事件
event Yank(uint256 id);

// --- Init ---
/// @notice 初始化拍卖合约
constructor(address vat_, address spotter_, address dog_, bytes32 ilk_) public {
vat = VatLike(vat_);
spotter = SpotterLike(spotter_);
dog = DogLike(dog_);
ilk = ilk_;
buf = RAY;
wards[msg.sender] = 1;
emit Rely(msg.sender);
}

// --- Synchronization ---
modifier lock() {
require(locked == 0, "Clipper/system-locked");
locked = 1;
_;
locked = 0;
}

modifier isStopped(uint256 level) {
require(stopped < level, "Clipper/stopped-incorrect");
_;
}

// --- Administration ---
/**
* @notice 修改系统参数 (权限控制)
* @param what 修改的参数名称
* @param data 修改的目标值
*/
function file(bytes32 what, uint256 data) external auth lock {
if (what == "buf") buf = data;
else if (what == "tail") tail = data; // Time elapsed before auction reset 拍卖重置前经历的时间

else if (what == "cusp") cusp = data; // Percentage drop before auction reset 拍卖重置前下降的百分比

/// @notice 对 keepper 的百分比 tab 激励 (最大值:2^64 - 1 => 18.xxx WAD = 18xx%
else if (what == "chip") chip = uint64(data); // Percentage of tab to incentivize (max: 2^64 - 1 => 18.xxx WAD = 18xx%)

/// @notice 对 keepper 的固定激励 (最大值:2^192 - 1 => 6.277T RAD)
else if (what == "tip") tip = uint192(data); // Flat fee to incentivize keepers (max: 2^192 - 1 => 6.277T RAD)

/// @notice 设置断路器
else if (what == "stopped") stopped = data; // Set breaker (0, 1, 2, or 3)

else revert("Clipper/file-unrecognized-param");
emit File(what, data);
}

/// @notice 修改特定配件合约的对应地址(权限控制)
function file(bytes32 what, address data) external auth lock {
if (what == "spotter") spotter = SpotterLike(data);
else if (what == "dog") dog = DogLike(data);
else if (what == "vow") vow = data;
else if (what == "calc") calc = AbacusLike(data);
else revert("Clipper/file-unrecognized-param");
emit File(what, data);
}

// --- Math ---
uint256 constant BLN = 10 ** 9;
uint256 constant WAD = 10 ** 18;
uint256 constant RAY = 10 ** 27;

function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = x <= y ? x : y;
}

function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x);
}

function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x);
}

function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x);
}

function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = mul(x, y) / WAD;
}

function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = mul(x, y) / RAY;
}

function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = mul(x, RAY) / y;
}

// --- Auction ---

// get the price directly from the OSM
// Could get this from rmul(Vat.ilks(ilk).spot, Spotter.mat()) instead, but
// if mat has changed since the last poke, the resulting value will be
// incorrect.
// 直接从 OSM 获取价格
// 也可以从 `rmul(Vat.ilks(ilk).spot, Spotter.mat())` 获取,但是
// 如果 mat 自上次 `poke` 后发生了变化,则结果值将
// 不正确。
function getFeedPrice() internal returns (uint256 feedPrice) {
(PipLike pip,) = spotter.ilks(ilk);
(bytes32 val, bool has) = pip.peek();
require(has, "Clipper/invalid-price");
feedPrice = rdiv(mul(uint256(val), BLN), spotter.par());
}

// start an auction
// note: trusts the caller to transfer collateral to the contract
// The starting price `top` is obtained as follows:
//
// top = val * buf / par
//
// Where `val` is the collateral's unitary value in USD, `buf` is a
// multiplicative factor to increase the starting price, and `par` is a
// reference per DAI.
/**
* @notice 开始拍卖
* @param tab 负债(要筹集的 Dai)
* @param lot 拍卖品数量
* @param usr 拍卖完成后,若有剩余的抵押品将被退还给 usr。usr 通常是被清算的用户。
* @param kpr 将接受激励的地址。keeper 是执行清算操作的实体,启动拍卖时,会收到一定的激励(tip 和 chip)。
* @notice
* - 注意:信任调用者将抵押品转移到合约
* - 起始价格 `top` 计算方法:
* top = val * buf / par
* - `val` 抵押品的美元单位价值
* - `buf` 增加起始价格的乘数
* - `par` 每个 DAI 的参考
* - 权限控制 `auth`,重入锁 `lock`,断路器满足 `level < 1` (允许开启新的拍卖)
* - 该函数只能由 `Dog` 合约调用。(keepper(清算实施者)--call--> `Dog` --call--> Clipper)
*/
function kick(
uint256 tab, // Debt [rad]
uint256 lot, // Collateral [wad]
address usr, // Address that will receive any leftover collateral
address kpr // Address that will receive incentives
) external auth lock isStopped(1) returns (uint256 id) {
// Input validation
require(tab > 0, "Clipper/zero-tab");
require(lot > 0, "Clipper/zero-lot");
require(usr != address(0), "Clipper/zero-usr");
id = ++kicks;
require(id > 0, "Clipper/overflow");

active.push(id);

sales[id].pos = active.length - 1;

sales[id].tab = tab;
sales[id].lot = lot;
sales[id].usr = usr;
sales[id].tic = uint96(block.timestamp);

uint256 top;
top = rmul(getFeedPrice(), buf);
require(top > 0, "Clipper/zero-top-price");
sales[id].top = top;

// incentive to kick auction 激励拍卖
uint256 _tip = tip;
uint256 _chip = chip;
uint256 coin;
if (_tip > 0 || _chip > 0) {
coin = add(_tip, wmul(tab, _chip)); // 计算 keepper 的激励
vat.suck(vow, kpr, coin);
}

emit Kick(id, top, tab, lot, usr, kpr, coin);
}

// Reset an auction
// See `kick` above for an explanation of the computation of `top`.
/**
* @notice 重置一个拍卖
* @param id 要进行重置的拍卖的 id
* @param kpr 重置拍卖的 keepper (清算者)(将要接收激励的地址)
* @notice
* - `lock` 重入锁,`isStopped(2)` 断路器满足条件 < 2
*/
function redo(
uint256 id, // id of the auction to reset
address kpr // Address that will receive incentives
) external lock isStopped(2) {
// Read auction data 读取拍卖数据
address usr = sales[id].usr;
uint96 tic = sales[id].tic;
uint256 top = sales[id].top;

require(usr != address(0), "Clipper/not-running-auction");

// Check that auction needs reset 检查拍卖是否需要重置
// and compute current price [ray] 并计算当前价格 [ray]
(bool done,) = status(tic, top);
require(done, "Clipper/cannot-reset");

uint256 tab = sales[id].tab;
uint256 lot = sales[id].lot;
sales[id].tic = uint96(block.timestamp);

uint256 feedPrice = getFeedPrice();
top = rmul(feedPrice, buf);
require(top > 0, "Clipper/zero-top-price");
sales[id].top = top;

// incentive to redo auction
// 重置拍卖的激励计算
uint256 _tip = tip;
uint256 _chip = chip;
uint256 coin;
if (_tip > 0 || _chip > 0) {
uint256 _chost = chost;
if (tab >= _chost && mul(lot, feedPrice) >= _chost) {
coin = add(_tip, wmul(tab, _chip));
vat.suck(vow, kpr, coin);
}
}

emit Redo(id, top, tab, lot, usr, kpr, coin);
}

// Buy up to `amt` of collateral from the auction indexed by `id`.
//
// Auctions will not collect more DAI than their assigned DAI target,`tab`;
// thus, if `amt` would cost more DAI than `tab` at the current price, the
// amount of collateral purchased will instead be just enough to collect `tab` DAI.
//
// To avoid partial purchases resulting in very small leftover auctions that will
// never be cleared, any partial purchase must leave at least `Clipper.chost`
// remaining DAI target. `chost` is an asynchronously updated value equal to
// (Vat.dust * Dog.chop(ilk) / WAD) where the values are understood to be determined
// by whatever they were when Clipper.upchost() was last called. Purchase amounts
// will be minimally decreased when necessary to respect this limit; i.e., if the
// specified `amt` would leave `tab < chost` but `tab > 0`, the amount actually
// purchased will be such that `tab == chost`.
//
// If `tab <= chost`, partial purchases are no longer possible; that is, the remaining
// collateral can only be purchased entirely, or not at all.
// 从由 `id` 索引的拍卖中购买最多 `amt` 的抵押品。
//
// 拍卖不会收集比其指定的 DAI 目标 `tab` 更多的 DAI;
// 因此,如果 `amt` 在当前价格下比 `tab` 花费更多的 DAI,则购买的抵押品数量将刚好足以收集 `tab` DAI。
//
// 为避免部分购买导致剩余拍卖非常少并且永远不会被清除,任何部分购买都必须至少留下
// `Clipper.chost` 剩余的 DAI 目标。`chost` 是一个异步更新的值,等于
// (Vat.dust * Dog.chop(ilk) / WAD),其中这些值被理解为由
// 上次调用 Clipper.upchost() 时的值决定。购买金额将在必要时最小程度地减少以遵守此限制;
// 即,如果指定的 `amt` 为 `tab < chost` 但 `tab > 0`,则实际购买的金额将为 `tab == chost`。
//
// 如果 `tab <= chost`,则不再可能进行部分购买;也就是说,剩余的
// 抵押品只能全部购买,或者根本无法购买。
/**
* @notice 拍卖函数(拍抵押品的函数)
* @param id 要拍的拍卖 id
* @param amt 购买抵押品数量的上限 [wad]
* @param max 最高可接受价格 (DAI / 抵押品) [ray]
* @param who 抵押品接收者和外部调用地址(拍卖的地址)
* @param data 传入的外部 calldata 数据,长度为 0 表示未进行调用
* @notice
* - `lock` 重入锁,`isStopped(3)` 断路器满足 < 3
*/
function take(
uint256 id, // Auction id
uint256 amt, // Upper limit on amount of collateral to buy [wad]
uint256 max, // Maximum acceptable price (DAI / collateral) [ray]
address who, // Receiver of collateral and external call address
bytes calldata data // Data to pass in external call; if length 0, no call is done
) external lock isStopped(3) {
address usr = sales[id].usr;
uint96 tic = sales[id].tic;

require(usr != address(0), "Clipper/not-running-auction");

uint256 price;
{
bool done;
(done, price) = status(tic, sales[id].top);

// Check that auction doesn't need reset
// 检测对应的拍卖是否需要重置
require(!done, "Clipper/needs-reset");
}

// Ensure price is acceptable to buyer
// 确保价格为买家所接受
require(max >= price, "Clipper/too-expensive");

uint256 lot = sales[id].lot;
uint256 tab = sales[id].tab;
uint256 owe;

{
// Purchase as much as possible, up to amt
// 尽可能多的买,最多为 `amt`
// 计算实际购买的抵押品数量,初始值为购买上限和拍卖中剩余抵押品数量中的最小值
uint256 slice = min(lot, amt); // slice <= lot

// DAI needed to buy a slice of this sale
// 购买这笔拍卖的 slice 需要的 DAI
owe = mul(slice, price);

// Don't collect more than tab of DAI
// 不收集超过 `tab` 目标的 DAI
if (owe > tab) {
// 如果所需 DAI 数量超过债务
owe = tab; // owe' <= owe 调整所需 DAI 数量为债务数量
// Adjust slice
// 调整实际购买的抵押品数量
slice = owe / price; // slice' = owe' / price <= owe / price == slice <= lot
} else if (owe < tab && slice < lot) {
// If slice == lot => auction completed => dust doesn't matter
uint256 _chost = chost; // 获取最小剩余债务目标
if (tab - owe < _chost) {
// safe as owe < tab 安全:因为 owe < tab
// If tab <= chost, buyers have to take the entire lot.
// 确保部分购买满足最小剩余债务目标
require(tab > _chost, "Clipper/no-partial-purchase");
// Adjust amount to pay
// 调整所需 DAI 数量
owe = tab - _chost; // owe' <= owe
// Adjust slice
// 调整实际购买抵押品数量
slice = owe / price; // slice' = owe' / price < owe / price == slice < lot
}
}

// Calculate remaining tab after operation
// 更新剩余债务
tab = tab - owe; // safe since owe <= tab 安全:因为 owe <= tab
// Calculate remaining lot after operation
// 更新剩余抵押品数量
lot = lot - slice;

// Send collateral to who
// 将抵押品发送给 `who`
vat.flux(ilk, address(this), who, slice);

// Do external call (if data is defined) but to be
// extremely careful we don't allow to do it to the two
// contracts which the Clipper needs to be authorized
// 若 data 被定义,执行外部调用。不允许 `who` 为 `vat` 和 `dog`
// vat 和 dog 对 Clipper 合约进行授权,所以限制用户,不能通过 low-level call
// 调用 vat 和 dog 合约避免出现非预期问题,避免出现:权限访问控制漏洞
DogLike dog_ = dog;
if (data.length > 0 && who != address(vat) && who != address(dog_)) {
ClipperCallee(who).clipperCall(msg.sender, owe, slice, data);
}

// Get DAI from caller
// 从调用者处获取 DAI
vat.move(msg.sender, vow, owe);

// Removes Dai out for liquidation from accumulator
// 从累加器中移除要清算的 DAI
dog_.digs(ilk, lot == 0 ? tab + owe : owe);
}

if (lot == 0) {
// 所有抵押品全部拍卖
_remove(id); // 直接移除对应的拍卖
} else if (tab == 0) {
// lot != 0 => 抵押品剩余
// 并且收取了目标 `tab` 的 DAI
vat.flux(ilk, address(this), usr, lot); // 将剩余的抵押品转移回被清算者
_remove(id); // 移除对应的拍卖
} else {
// 都不满足,证明拍卖还需要继续
// 更新拍卖状态(剩余需要拍卖的目标,剩余的抵押品数量)
sales[id].tab = tab;
sales[id].lot = lot;
}

emit Take(id, max, price, owe, tab, lot, usr);
}

function _remove(uint256 id) internal {
uint256 _move = active[active.length - 1]; // 读出 active 活跃拍卖id动态数组中的最后一个元素(最后一个拍卖 `id`)
// 若所要删除的活跃拍卖 `id` 与 `active` 数组中的最后一个拍卖 `id`不等:执行 if 代码块的修改
// 若相等(二者是一个),直接 `pop` 弹出
if (id != _move) {
// 读取要移除的`id`对应的活跃拍卖id数组的 index
uint256 _index = sales[id].pos;
// 将最后一个元素(拍卖` id` )存到要删除的序号的位置
active[_index] = _move;
// 修改最后一个元素对应的拍卖(`sales[_move]`)在 `sales` mapping 中的 `pos`(对应活跃拍卖 id 数组中的 index)
sales[_move].pos = _index;
}
active.pop(); // 删除动态数组的最后一个元素
delete sales[id]; // 删除对应 `id` 的 `sales` mapping 中的对应条目
}

// The number of active auctions
/**
* @notice 返回目前活跃拍卖的数量
* @return 返回 active 数组的长度
*/
function count() external view returns (uint256) {
return active.length;
}

// Return the entire array of active auctions
/**
* @notice 返回全部活跃拍卖
* @return 整个 active 数组
*/
function list() external view returns (uint256[] memory) {
return active;
}

// Externally returns boolean for if an auction needs a redo and also the current price
/**
* @notice 外部返回布尔值,表示拍卖是否需要重新拍卖以及当前价格
* @param id 对应拍卖 id
* @return needsRedo 是否需要重新拍卖
* @return price 没有重新拍卖时的价格
* @return lot 没有重新拍卖时的总抵押品数量
* @return tab 没有重新拍卖时的所需筹集 DAI 的目标
*/
function getStatus(uint256 id) external view returns (bool needsRedo, uint256 price, uint256 lot, uint256 tab) {
// Read auction data
address usr = sales[id].usr;
uint96 tic = sales[id].tic;

bool done;
(done, price) = status(tic, sales[id].top);

needsRedo = usr != address(0) && done;
lot = sales[id].lot;
tab = sales[id].tab;
}

// Internally returns boolean for if an auction needs a redo
/**
* @dev 内部函数,计算拍卖是否需要重置
* @param tic 拍卖开始的时间
* @param top 拍卖开始时的金额 [ray]
* @return done 拍卖是否需要重置
* @return price 当前拍卖价格
* @notice
* - 只需要满足下面两个条件之一即可重置拍卖(返回值为 `true`)
* - 拍卖时间超过了最长时间 `tail`
* - 拍卖价格下降的百分比低于了 `cusp`
*/
function status(uint96 tic, uint256 top) internal view returns (bool done, uint256 price) {
price = calc.price(top, sub(block.timestamp, tic));
done = (sub(block.timestamp, tic) > tail || rdiv(price, top) < cusp);
}

// Public function to update the cached dust*chop value.
/// @notice 用于更新缓存的 dust*chop 值的公共函数。
function upchost() external {
(,,,, uint256 _dust) = VatLike(vat).ilks(ilk);
chost = wmul(_dust, dog.chop(ilk));
}

// Cancel an auction during ES or via governance action.
///
/**
* @dev 在 ES 期间或通过治理行动取消拍卖。
* @param id 要取消的拍卖的拍卖 id
* @notice 该函数只能由只在 `end` 合约中调用,`end` 合约负责系统的关停
* 函数 `End.snip` 负责在触发关闭时调用 `Clipper.yank` 来关闭任何正在运行的拍卖。
* 他将收到 `Clipper.yank` 的抵押品,并将其剩余债务一起发送回 Vault 所有者进行追偿。
* 注意:Vault 所有者将收到的债务包括已经收取的清算罚款部分。
*/
function yank(uint256 id) external auth lock {
require(sales[id].usr != address(0), "Clipper/not-running-auction");
// 从累加器中移除要清算的 DAI
dog.digs(ilk, sales[id].tab);
// 将抵押品转回 `msg.sender`
vat.flux(ilk, address(this), msg.sender, sales[id].lot);
_remove(id);
emit Yank(id);
}
}