Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider liquidation price on take #219

Open
wants to merge 2 commits into
base: liq-2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/clip.sol
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ contract Clipper {
function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = mul(x, RAY) / y;
}
function max(uint x, uint y) internal pure returns (uint z) {
return x >= y ? x : y;
}


// --- Auction ---
Expand All @@ -212,15 +215,15 @@ contract Clipper {
function getPrice() internal returns (uint256 price) {
(PipLike pip, ) = spotter.ilks(ilk);
(bytes32 val, bool has) = pip.peek();
require(has, "Clipper/invalid-price");
require(has && uint256(val) > 0, "Clipper/invalid-price");
price = 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
// top = max( val * buf / par , tab / lot )
//
// Where `val` is the collateral's unitary value in USD, `buf` is a
// multiplicative factor to increase the starting price, and `par` is a
Expand Down Expand Up @@ -248,7 +251,7 @@ contract Clipper {
sales[id].tic = uint96(block.timestamp);

uint256 top;
top = rmul(getPrice(), buf);
top = max(rmul(getPrice(), buf), tab / lot);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using tab / lot isn't optimal because it neglects mat. See Lucas's reply to me here:

#158 (comment)

From discussions w/Primoz, having some multiplier (i.e. a second buf) is the most flexible option. See e.g.:
https://forum.makerdao.com/t/mip45-liquidations-2-0-liq-2-0-liquidation-system-redesign/6352/25

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still thinking through this. I can see Lucas's point, but I see this as an emergency fallback, the oracle price will still be primarily used as the starting point unless there's a hugely dramatic drop.

Primoz has a point, too, but rather than reducing it by a multiplier I'd argue for Lucas' point that this is just a starting point for the auction and that at least gives the vaultholder a fair shot at recapitalizing in the market. If indeed it is too low, it should still be repriced in the redo()

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

Expand Down
87 changes: 87 additions & 0 deletions src/test/clip.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,92 @@ contract ClipperTest is DSTest {
assertEq(vat.dai(bob), rad(1000 ether) + rad(100 ether) + tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark()
}

function test_low_osm_price_kick() public {
uint256 pos;
uint256 tab;
uint256 lot;
address usr;
uint96 tic;
uint256 top;
uint256 ink;
uint256 art;

clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI
clip.file("chip", 0); // No linear increase

assertEq(clip.kicks(), 0);
(pos, tab, lot, usr, tic, top) = clip.sales(1);
assertEq(pos, 0);
assertEq(tab, 0);
assertEq(lot, 0);
assertEq(usr, address(0));
assertEq(uint256(tic), 0);
assertEq(top, 0);
assertEq(vat.gem(ilk, me), 960 ether);
assertEq(vat.dai(ali), rad(1000 ether));
(ink, art) = vat.urns(ilk, me);
assertEq(ink, 40 ether);
assertEq(art, 100 ether);

Guy(ali).bark(dog, ilk, me, address(ali));

assertEq(clip.kicks(), 1);
(pos, tab, lot, usr, tic, top) = clip.sales(1);
assertEq(pos, 0);
assertEq(tab, rad(110 ether));
assertEq(lot, 40 ether);
assertEq(usr, me);
assertEq(uint256(tic), now);
assertEq(top, ray(4 ether));
assertEq(vat.gem(ilk, me), 960 ether);
assertEq(vat.dai(ali), rad(1100 ether)); // Paid "tip" amount of DAI for calling bark()
(ink, art) = vat.urns(ilk, me);
assertEq(ink, 0 ether);
assertEq(art, 0 ether);

pip.poke(bytes32(goldPrice)); // Spot = $2.5
spot.poke(ilk); // Now safe

hevm.warp(startTime + 100);
vat.frob(ilk, me, me, me, 40 ether, 100 ether);

pip.poke(bytes32(uint256(1))); // Spot = 1 wei attack
spot.poke(ilk); // Now unsafe

(pos, tab, lot, usr, tic, top) = clip.sales(2);
assertEq(pos, 0);
assertEq(tab, 0);
assertEq(lot, 0);
assertEq(usr, address(0));
assertEq(uint256(tic), 0);
assertEq(top, 0);
assertEq(vat.gem(ilk, me), 920 ether);

clip.file(bytes32("buf"), ray(1.25 ether)); // 25% Initial price buffer

clip.file("tip", rad(100 ether)); // Flat fee of 100 DAI
clip.file("chip", 0.02 ether); // Linear increase of 2% of tab

assertEq(vat.dai(bob), rad(1000 ether));

Guy(bob).bark(dog, ilk, me, address(bob));

assertEq(clip.kicks(), 2);
(pos, tab, lot, usr, tic, top) = clip.sales(2);
assertEq(pos, 1);
assertEq(tab, rad(110 ether));
assertEq(lot, 40 ether);
assertEq(usr, me);
assertEq(uint256(tic), now);
assertEq(top, ray(2.75 ether)); // Starting amount protected by imputed price in attack
assertEq(vat.gem(ilk, me), 920 ether);
(ink, art) = vat.urns(ilk, me);
assertEq(ink, 0 ether);
assertEq(art, 0 ether);

assertEq(vat.dai(bob), rad(1000 ether) + rad(100 ether) + tab * 0.02 ether / WAD); // Paid (tip + due * chip) amount of DAI for calling bark()
}

function testFail_kick_zero_price() public {
pip.poke(bytes32(0));
dog.bark(ilk, me, address(this));
Expand Down Expand Up @@ -1581,4 +1667,5 @@ contract ClipperTest is DSTest {
data: ""
});
}

}