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

Change vow to split out accounting from auction logic #243

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
170 changes: 170 additions & 0 deletions src/bow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

/// bow.sol -- Dai settlement module

// Copyright (C) 2018 Rain <[email protected]>
// Copyright (C) 2021-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;

interface FlopLike {
function kick(address gal, uint256 lot, uint256 bid) external returns (uint256);
function cage() external;
function live() external returns (uint256);
}

interface FlapLike {
function kick(uint256 lot, uint256 bid) external returns (uint256);
function cage(uint256) external;
function live() external returns (uint256);
}

interface VatLike {
function dai (address) external view returns (uint256);
function sin (address) external view returns (uint256);
function heal(uint256) external;
function hope(address) external;
function nope(address) external;
function move(address, address, uint256) external;
}

interface VowLike {
function heal(uint256) external;
}

contract Bow {

// --- Auth ---
mapping (address => uint256) public wards;
function rely(address usr) external auth { require(live == 1, "Bow/not-live"); 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, "Bow/not-authorized");
_;
}

// --- Data ---
VatLike public immutable vat; // CDP Engine
address public immutable vow; // System accounting
FlapLike public flapper; // Surplus Auction House
FlopLike public flopper; // Debt Auction House

mapping (uint256 => uint256) public sin; // debt queue
uint256 public Sin; // Queued debt [rad]
uint256 public Ash; // On-auction debt [rad]

uint256 public wait; // Flop delay [seconds]
uint256 public dump; // Flop initial lot size [wad]
uint256 public sump; // Flop fixed bid size [rad]

uint256 public bump; // Flap fixed lot size [rad]
uint256 public hump; // Surplus buffer [rad]

uint256 public live; // Active Flag

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

// --- Init ---
constructor(address vat_, address vow_, address flapper_, address flopper_) public {
vat = VatLike(vat_);
vow = vow_;
flapper = FlapLike(flapper_);
flopper = FlopLike(flopper_);

VatLike(vat_).hope(flapper_);
live = 1;
wards[msg.sender] = 1;
emit Rely(msg.sender);
}

// --- Math ---
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 min(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x <= y ? x : y;
}

// --- Administration ---
function file(bytes32 what, uint256 data) external auth {
if (what == "wait") wait = data;
else if (what == "bump") bump = data;
else if (what == "sump") sump = data;
else if (what == "dump") dump = data;
else if (what == "hump") hump = data;
else revert("Bow/file-unrecognized-param");
}

function file(bytes32 what, address data) external auth {
if (what == "flapper") {
vat.nope(address(flapper));
flapper = FlapLike(data);
vat.hope(data);
}
else if (what == "flopper") flopper = FlopLike(data);
else revert("Bow/file-unrecognized-param");
}

// Push to debt-queue
function fess(uint256 tab) external auth {
sin[block.timestamp] = add(sin[block.timestamp], tab);
Sin = add(Sin, tab);
}
// Pop from debt-queue
function flog(uint256 era) external {
require(add(era, wait) <= block.timestamp, "Bow/wait-not-finished");
Sin = sub(Sin, sin[era]);
sin[era] = 0;
}

// Debt settlement
function kiss(uint256 rad) external {
require(rad <= Ash, "Bow/not-enough-ash");
require(rad <= vat.dai(vow), "Bow/insufficient-surplus");
Ash = sub(Ash, rad);
vat.heal(rad);
}

// Debt auction
function flop() external returns (uint256 id) {
require(sump <= sub(sub(vat.sin(vow), Sin), Ash), "Bow/insufficient-debt");
require(vat.dai(vow) == 0, "Bow/surplus-not-zero");
Ash = add(Ash, sump);
id = flopper.kick(vow, dump, sump);
}
// Surplus auction
function flap() external returns (uint256 id) {
require(vat.dai(vow) >= add(add(vat.sin(vow), bump), hump), "Bow/insufficient-surplus");
require(sub(sub(vat.sin(vow), Sin), Ash) == 0, "Bow/debt-not-zero");
vat.move(msg.sender, address(this), bump);
id = flapper.kick(bump, 0);
}

function cage() external auth {
require(live == 1, "Bow/not-live");
live = 0;
Sin = 0;
Ash = 0;
flapper.cage(vat.dai(address(flapper)));
flopper.cage();
VowLike(vow).heal(min(vat.dai(vow), vat.sin(vow)));
}
}
70 changes: 37 additions & 33 deletions src/test/vow.t.sol → src/test/bow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Flopper as Flop} from './flop.t.sol';
import {Flapper as Flap} from './flap.t.sol';
import {TestVat as Vat} from './vat.t.sol';
import {Vow} from '../vow.sol';
import {Bow} from '../bow.sol';

interface Hevm {
function warp(uint256) external;
Expand All @@ -20,11 +21,12 @@ contract Gem {
}
}

contract VowTest is DSTest {
contract BowTest is DSTest {
Hevm hevm;

Vat vat;
Vow vow;
Bow bow;
Flop flop;
Flap flap;
Gem gov;
Expand All @@ -39,20 +41,22 @@ contract VowTest is DSTest {
flop = new Flop(address(vat), address(gov));
flap = new Flap(address(vat), address(gov));

vow = new Vow(address(vat), address(flap), address(flop));
flap.rely(address(vow));
flop.rely(address(vow));
vow = new Vow(address(vat));
bow = new Bow(address(vat), address(vow), address(flap), address(flop));
vow.hope(address(bow));
flap.rely(address(bow));
flop.rely(address(bow));

vow.file("bump", rad(100 ether));
vow.file("sump", rad(100 ether));
vow.file("dump", 200 ether);
bow.file("bump", rad(100 ether));
bow.file("sump", rad(100 ether));
bow.file("dump", 200 ether);

vat.hope(address(flop));
}

function try_flog(uint era) internal returns (bool ok) {
string memory sig = "flog(uint256)";
(ok,) = address(vow).call(abi.encodeWithSignature(sig, era));
(ok,) = address(bow).call(abi.encodeWithSignature(sig, era));
}
function try_dent(uint id, uint lot, uint bid) internal returns (bool ok) {
string memory sig = "dent(uint256,uint256,uint256)";
Expand All @@ -72,7 +76,7 @@ contract VowTest is DSTest {
string memory sig = "flap()";
bytes memory data = abi.encodeWithSignature(sig);

bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data);
bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", bow, data);
(bool ok, bytes memory success) = address(this).call(can_call);

ok = abi.decode(success, (bool));
Expand All @@ -82,7 +86,7 @@ contract VowTest is DSTest {
string memory sig = "flop()";
bytes memory data = abi.encodeWithSignature(sig);

bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data);
bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", bow, data);
(bool ok, bytes memory success) = address(this).call(can_call);

ok = abi.decode(success, (bool));
Expand All @@ -95,13 +99,13 @@ contract VowTest is DSTest {
}

function suck(address who, uint wad) internal {
vow.fess(rad(wad));
bow.fess(rad(wad));
vat.init('');
vat.suck(address(vow), who, rad(wad));
}
function flog(uint wad) internal {
suck(address(0), wad); // suck dai into the zero address
vow.flog(now);
bow.flog(now);
}
function heal(uint wad) internal {
vow.heal(rad(wad));
Expand All @@ -111,29 +115,29 @@ contract VowTest is DSTest {
Flap newFlap = new Flap(address(vat), address(gov));
Flop newFlop = new Flop(address(vat), address(gov));

newFlap.rely(address(vow));
newFlop.rely(address(vow));
newFlap.rely(address(bow));
newFlop.rely(address(bow));

assertEq(vat.can(address(vow), address(flap)), 1);
assertEq(vat.can(address(vow), address(newFlap)), 0);
assertEq(vat.can(address(bow), address(flap)), 1);
assertEq(vat.can(address(bow), address(newFlap)), 0);

vow.file('flapper', address(newFlap));
vow.file('flopper', address(newFlop));
bow.file('flapper', address(newFlap));
bow.file('flopper', address(newFlop));

assertEq(address(vow.flapper()), address(newFlap));
assertEq(address(vow.flopper()), address(newFlop));
assertEq(address(bow.flapper()), address(newFlap));
assertEq(address(bow.flopper()), address(newFlop));

assertEq(vat.can(address(vow), address(flap)), 0);
assertEq(vat.can(address(vow), address(newFlap)), 1);
assertEq(vat.can(address(bow), address(flap)), 0);
assertEq(vat.can(address(bow), address(newFlap)), 1);
}

function test_flog_wait() public {
assertEq(vow.wait(), 0);
vow.file('wait', uint(100 seconds));
assertEq(vow.wait(), 100 seconds);
assertEq(bow.wait(), 0);
bow.file('wait', uint(100 seconds));
assertEq(bow.wait(), 100 seconds);

uint tic = now;
vow.fess(100 ether);
bow.fess(100 ether);
hevm.warp(tic + 99 seconds);
assertTrue(!try_flog(tic) );
hevm.warp(tic + 100 seconds);
Expand All @@ -143,7 +147,7 @@ contract VowTest is DSTest {
function test_no_reflop() public {
flog(100 ether);
assertTrue( can_flop() );
vow.flop();
bow.flop();
assertTrue(!can_flop() );
}

Expand All @@ -163,29 +167,29 @@ contract VowTest is DSTest {
}

function test_no_flap_pending_sin() public {
vow.file("bump", uint256(0 ether));
bow.file("bump", uint256(0 ether));
flog(100 ether);

vat.mint(address(vow), 50 ether);
assertTrue(!can_flap() );
}
function test_no_flap_nonzero_woe() public {
vow.file("bump", uint256(0 ether));
bow.file("bump", uint256(0 ether));
flog(100 ether);
vat.mint(address(vow), 50 ether);
assertTrue(!can_flap() );
}
function test_no_flap_pending_flop() public {
flog(100 ether);
vow.flop();
bow.flop();

vat.mint(address(vow), 100 ether);

assertTrue(!can_flap() );
}
function test_no_flap_pending_heal() public {
flog(100 ether);
uint id = vow.flop();
uint id = bow.flop();

vat.mint(address(this), 100 ether);
flop.dent(id, 0 ether, rad(100 ether));
Expand All @@ -195,7 +199,7 @@ contract VowTest is DSTest {

function test_no_surplus_after_good_flop() public {
flog(100 ether);
uint id = vow.flop();
uint id = bow.flop();
vat.mint(address(this), 100 ether);

flop.dent(id, 0 ether, rad(100 ether)); // flop succeeds..
Expand All @@ -205,7 +209,7 @@ contract VowTest is DSTest {

function test_multiple_flop_dents() public {
flog(100 ether);
uint id = vow.flop();
uint id = bow.flop();

vat.mint(address(this), 100 ether);
assertTrue(try_dent(id, 2 ether, rad(100 ether)));
Expand Down
2 changes: 1 addition & 1 deletion src/test/clip.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ contract ClipperTest is DSTest {
spot = new Spotter(address(vat));
vat.rely(address(spot));

vow = new Vow(address(vat), address(0), address(0));
vow = new Vow(address(vat));
gold = new DSToken("GLD");
goldJoin = new GemJoin(address(vat), ilk, address(gold));
vat.rely(address(goldJoin));
Expand Down
Loading