From bad0d7acc1c64c7ededa54f63fefefab9c96411f Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Mon, 27 Dec 2021 14:32:10 -0500 Subject: [PATCH 1/2] started conversion to split vow accounting / auction logic --- src/bow.sol | 170 ++++++++++++++++++++++++++++++ src/test/{vow.t.sol => bow.t.sol} | 70 ++++++------ src/test/clip.t.sol | 2 +- src/test/end.t.sol | 24 +++-- src/test/vat.t.sol | 116 ++++++++++---------- src/vow.sol | 143 +++++-------------------- 6 files changed, 308 insertions(+), 217 deletions(-) create mode 100644 src/bow.sol rename src/test/{vow.t.sol => bow.t.sol} (77%) diff --git a/src/bow.sol b/src/bow.sol new file mode 100644 index 00000000..88c46781 --- /dev/null +++ b/src/bow.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/// bow.sol -- Dai settlement module + +// Copyright (C) 2018 Rain +// 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 . + +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))); + } +} diff --git a/src/test/vow.t.sol b/src/test/bow.t.sol similarity index 77% rename from src/test/vow.t.sol rename to src/test/bow.t.sol index 33e4cb87..1544aa28 100644 --- a/src/test/vow.t.sol +++ b/src/test/bow.t.sol @@ -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; @@ -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; @@ -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)"; @@ -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)); @@ -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)); @@ -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)); @@ -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); @@ -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() ); } @@ -163,21 +167,21 @@ 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); @@ -185,7 +189,7 @@ contract VowTest is DSTest { } 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)); @@ -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.. @@ -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))); diff --git a/src/test/clip.t.sol b/src/test/clip.t.sol index db31b4ec..9d3eb002 100644 --- a/src/test/clip.t.sol +++ b/src/test/clip.t.sol @@ -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)); diff --git a/src/test/end.t.sol b/src/test/end.t.sol index f574e208..1717fe8f 100644 --- a/src/test/end.t.sol +++ b/src/test/end.t.sol @@ -28,6 +28,7 @@ import {Vat} from '../vat.sol'; import {Cat} from '../cat.sol'; import {Dog} from '../dog.sol'; import {Vow} from '../vow.sol'; +import {Bow} from '../bow.sol'; import {Pot} from '../pot.sol'; import {Flipper} from '../flip.sol'; import {Clipper} from '../clip.sol'; @@ -81,6 +82,7 @@ contract EndTest is DSTest { Vat vat; End end; Vow vow; + Bow bow; Pot pot; Cat cat; Dog dog; @@ -208,19 +210,21 @@ contract EndTest is DSTest { flop = new Flopper(address(vat), address(gov)); gov.setOwner(address(flop)); - vow = new Vow(address(vat), address(flap), address(flop)); + vow = new Vow(address(vat)); + bow = new Bow(address(vat), address(vow), address(flap), address(flop)); + vow.hope(address(bow)); pot = new Pot(address(vat)); vat.rely(address(pot)); pot.file("vow", address(vow)); cat = new Cat(address(vat)); - cat.file("vow", address(vow)); + cat.file("vow", address(bow)); vat.rely(address(cat)); vow.rely(address(cat)); dog = new Dog(address(vat)); - dog.file("vow", address(vow)); + dog.file("vow", address(bow)); vat.rely(address(dog)); vow.rely(address(dog)); @@ -232,7 +236,7 @@ contract EndTest is DSTest { end.file("vat", address(vat)); end.file("cat", address(cat)); end.file("dog", address(dog)); - end.file("vow", address(vow)); + end.file("vow", address(bow)); end.file("pot", address(pot)); end.file("spot", address(spot)); end.file("wait", 1 hours); @@ -250,18 +254,18 @@ contract EndTest is DSTest { assertEq(end.live(), 1); assertEq(vat.live(), 1); assertEq(cat.live(), 1); - assertEq(vow.live(), 1); + assertEq(bow.live(), 1); assertEq(pot.live(), 1); - assertEq(vow.flopper().live(), 1); - assertEq(vow.flapper().live(), 1); + assertEq(bow.flopper().live(), 1); + assertEq(bow.flapper().live(), 1); end.cage(); assertEq(end.live(), 0); assertEq(vat.live(), 0); assertEq(cat.live(), 0); - assertEq(vow.live(), 0); + assertEq(bow.live(), 0); assertEq(pot.live(), 0); - assertEq(vow.flopper().live(), 0); - assertEq(vow.flapper().live(), 0); + assertEq(bow.flopper().live(), 0); + assertEq(bow.flapper().live(), 0); } function test_cage_pot_drip() public { diff --git a/src/test/vat.t.sol b/src/test/vat.t.sol index f5d65c91..efa0c140 100644 --- a/src/test/vat.t.sol +++ b/src/test/vat.t.sol @@ -8,6 +8,7 @@ import "ds-token/token.sol"; import {Vat} from '../vat.sol'; import {Cat} from '../cat.sol'; import {Vow} from '../vow.sol'; +import {Bow} from '../bow.sol'; import {Jug} from '../jug.sol'; import {GemJoin, DaiJoin} from '../join.sol'; @@ -29,16 +30,16 @@ contract TestVat is Vat { } } -contract TestVow is Vow { - constructor(address vat, address flapper, address flopper) - public Vow(vat, flapper, flopper) {} +contract TestBow is Bow { + constructor(address vat, address vow, address flapper, address flopper) + public Bow(vat, vow, flapper, flopper) {} // Total deficit function Awe() public view returns (uint) { - return vat.sin(address(this)); + return vat.sin(vow); } // Total surplus function Joy() public view returns (uint) { - return vat.dai(address(this)); + return vat.dai(vow); } // Unqueued, pre-auction debt function Woe() public view returns (uint) { @@ -426,7 +427,8 @@ contract BiteTest is DSTest { Hevm hevm; TestVat vat; - TestVow vow; + Vow vow; + TestBow bow; Cat cat; DSToken gold; Jug jug; @@ -484,9 +486,11 @@ contract BiteTest is DSTest { flap = new Flapper(address(vat), address(gov)); flop = new Flopper(address(vat), address(gov)); - vow = new TestVow(address(vat), address(flap), address(flop)); - flap.rely(address(vow)); - flop.rely(address(vow)); + vow = new Vow(address(vat)); + bow = new TestBow(address(vat), address(vow), address(flap), address(flop)); + vow.hope(address(bow)); + flap.rely(address(bow)); + flop.rely(address(bow)); jug = new Jug(address(vat)); jug.init("gold"); @@ -497,7 +501,7 @@ contract BiteTest is DSTest { cat.file("vow", address(vow)); cat.file("box", rad((10 ether) * MLN)); vat.rely(address(cat)); - vow.rely(address(cat)); + bow.rely(address(cat)); gold = new DSToken("GEM"); gold.mint(1000 ether); @@ -556,7 +560,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 0); assertEq(art("gold", address(this)), 0); // all debt goes to the vow - assertEq(vow.Awe(), rad(100 ether)); + assertEq(bow.Awe(), rad(100 ether)); // auction is for all collateral (, uint lot,,,,,, uint tab) = flip.bids(auction); assertEq(lot, 40 ether); @@ -576,7 +580,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 10 ether); assertEq(art("gold", address(this)), 25 ether); // a fraction of the debt goes to the vow - assertEq(vow.Awe(), rad(75 ether)); + assertEq(bow.Awe(), rad(75 ether)); // auction is for a fraction of the collateral (, uint lot,,,,,, uint tab) = FlipLike(address(flip)).bids(auction); assertEq(lot, 30 ether); @@ -595,7 +599,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 40 ether); assertEq(art("gold", address(this)), 100 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 960 ether); cat.file("gold", "dunk", rad(200 ether)); // => bite everything @@ -604,7 +608,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(110 ether)); assertEq(ink("gold", address(this)), 0); assertEq(art("gold", address(this)), 0); - assertEq(vow.sin(now), rad(100 ether)); + assertEq(bow.sin(now), rad(100 ether)); assertEq(gem("gold", address(this)), 960 ether); assertEq(vat.dai(address(vow)), rad(0 ether)); @@ -617,7 +621,7 @@ contract BiteTest is DSTest { flip.dent(auction, 38 ether, rad(110 ether)); assertEq(vat.dai(address(this)), rad(90 ether)); assertEq(gem("gold", address(this)), 962 ether); - assertEq(vow.sin(now), rad(100 ether)); + assertEq(bow.sin(now), rad(100 ether)); hevm.warp(now + 4 hours); assertEq(cat.litter(), rad(110 ether)); @@ -638,7 +642,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); cat.file("box", rad(75 ether)); @@ -649,7 +653,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 50 ether); assertEq(art("gold", address(this)), 75 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); assertEq(gem("gold", address(this)), 900 ether); assertEq(vat.dai(address(this)), rad(150 ether)); @@ -665,7 +669,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(vat.dai(address(this)), rad(75 ether)); assertEq(gem("gold", address(this)), 925 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); hevm.warp(now + 4 hours); flip.deal(auction); @@ -688,7 +692,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); // To check this yourself, use the following rate calculation (example 8%): @@ -717,7 +721,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether - dink); // Taken in vat.grab assertEq(art("gold", address(this)), 150 ether - dart); // Taken in vat.grab - assertEq(vow.sin(now), dart * rate); + assertEq(bow.sin(now), dart * rate); assertEq(gem("gold", address(this)), 900 ether); assertEq(vat.dai(address(this)), rad(150 ether)); @@ -733,7 +737,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), tab); assertEq(vat.dai(address(this)), rad(150 ether) - tab); assertEq(gem("gold", address(this)), 900 ether + (dink - 25 ether)); - assertEq(vow.sin(now), dart * rate); + assertEq(bow.sin(now), dart * rate); hevm.warp(now + 4 hours); flip.deal(auction); @@ -755,7 +759,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); cat.file("box", rad(75 ether)); @@ -766,7 +770,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(ink("gold", address(this)), 50 ether); assertEq(art("gold", address(this)), 75 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); assertEq(gem("gold", address(this)), 900 ether); // this bite puts us over the litterbox @@ -785,7 +789,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 50 ether); assertEq(art("gold", address(this)), 80 ether + 1); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 950 ether); cat.file("box", rad(100 ether)); @@ -798,7 +802,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(80 ether + 1)); // room is now dusty assertEq(ink("gold", address(this)), 0 ether); assertEq(art("gold", address(this)), 0 ether); - assertEq(vow.sin(now), rad(80 ether + 1)); + assertEq(bow.sin(now), rad(80 ether + 1)); assertEq(gem("gold", address(this)), 950 ether); // spot = tag / (par . mat) @@ -811,7 +815,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 850 ether); assertTrue(cat.box() - cat.litter() < rad(20 ether)); // room < dust @@ -832,7 +836,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); cat.file("box", rad(75 ether)); @@ -843,7 +847,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(ink("gold", address(this)), 50 ether); assertEq(art("gold", address(this)), 75 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); assertEq(gem("gold", address(this)), 900 ether); assertEq(vat.dai(address(this)), rad(150 ether)); @@ -859,7 +863,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(vat.dai(address(this)), rad(75 ether)); assertEq(gem("gold", address(this)), 925 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); // From testFail_fill_litterbox() we know another bite() here would // fail with a 'Cat/liquidation-limit-hit' revert. So let's deal() @@ -877,7 +881,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(ink("gold", address(this)), 0); assertEq(art("gold", address(this)), 0); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); assertEq(gem("gold", address(this)), 950 ether); assertEq(vat.dai(address(this)), rad(75 ether)); @@ -893,7 +897,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(vat.dai(address(this)), 0); assertEq(gem("gold", address(this)), 975 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); hevm.warp(now + 4 hours); flip.deal(auction); @@ -997,7 +1001,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); cat.file("gold", "dunk", rad(75 ether)); @@ -1006,7 +1010,7 @@ contract BiteTest is DSTest { assertEq(cat.litter(), rad(75 ether)); assertEq(ink("gold", address(this)), 50 ether); assertEq(art("gold", address(this)), 75 ether); - assertEq(vow.sin(now), rad(75 ether)); + assertEq(bow.sin(now), rad(75 ether)); assertEq(gem("gold", address(this)), 900 ether); vat.file("gold", 'spot', 0); @@ -1023,7 +1027,7 @@ contract BiteTest is DSTest { assertEq(ink("gold", address(this)), 100 ether); assertEq(art("gold", address(this)), 150 ether); - assertEq(vow.Woe(), 0 ether); + assertEq(bow.Woe(), 0 ether); assertEq(gem("gold", address(this)), 900 ether); cat.file("gold", "dunk", rad(75 ether)); @@ -1039,27 +1043,27 @@ contract BiteTest is DSTest { vat.file("gold", 'spot', ray(2 ether)); // now unsafe cat.file("gold", "dunk", rad(200 ether)); // => bite everything - assertEq(vow.sin(now), rad( 0 ether)); + assertEq(bow.sin(now), rad( 0 ether)); cat.bite("gold", address(this)); - assertEq(vow.sin(now), rad(100 ether)); - - assertEq(vow.Sin(), rad(100 ether)); - vow.flog(now); - assertEq(vow.Sin(), rad( 0 ether)); - assertEq(vow.Woe(), rad(100 ether)); - assertEq(vow.Joy(), rad( 0 ether)); - assertEq(vow.Ash(), rad( 0 ether)); - - vow.file("sump", rad(10 ether)); - vow.file("dump", 2000 ether); - uint f1 = vow.flop(); - assertEq(vow.Woe(), rad(90 ether)); - assertEq(vow.Joy(), rad( 0 ether)); - assertEq(vow.Ash(), rad(10 ether)); + assertEq(bow.sin(now), rad(100 ether)); + + assertEq(bow.Sin(), rad(100 ether)); + bow.flog(now); + assertEq(bow.Sin(), rad( 0 ether)); + assertEq(bow.Woe(), rad(100 ether)); + assertEq(bow.Joy(), rad( 0 ether)); + assertEq(bow.Ash(), rad( 0 ether)); + + bow.file("sump", rad(10 ether)); + bow.file("dump", 2000 ether); + uint f1 = bow.flop(); + assertEq(bow.Woe(), rad(90 ether)); + assertEq(bow.Joy(), rad( 0 ether)); + assertEq(bow.Ash(), rad(10 ether)); flop.dent(f1, 1000 ether, rad(10 ether)); - assertEq(vow.Woe(), rad(90 ether)); - assertEq(vow.Joy(), rad( 0 ether)); - assertEq(vow.Ash(), rad( 0 ether)); + assertEq(bow.Woe(), rad(90 ether)); + assertEq(bow.Joy(), rad( 0 ether)); + assertEq(bow.Ash(), rad( 0 ether)); assertEq(gov.balanceOf(address(this)), 100 ether); hevm.warp(now + 4 hours); @@ -1074,9 +1078,9 @@ contract BiteTest is DSTest { assertEq(vat.dai(address(vow)), rad(100 ether)); assertEq(gov.balanceOf(address(this)), 100 ether); - vow.file("bump", rad(100 ether)); - assertEq(vow.Awe(), 0 ether); - uint id = vow.flap(); + bow.file("bump", rad(100 ether)); + assertEq(bow.Awe(), 0 ether); + uint id = bow.flap(); assertEq(vat.dai(address(this)), rad(0 ether)); assertEq(gov.balanceOf(address(this)), 100 ether); diff --git a/src/vow.sol b/src/vow.sol index d34a502c..ddb78f5f 100644 --- a/src/vow.sol +++ b/src/vow.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -/// vow.sol -- Dai settlement module +/// vow.sol -- Dai system accounting module // Copyright (C) 2018 Rain // @@ -17,147 +17,56 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pragma solidity >=0.5.12; - -// FIXME: This contract was altered compared to the production version. -// It doesn't use LibNote anymore. -// New deployments of this contract will need to include custom events (TO DO). - -interface FlopLike { - function kick(address gal, uint lot, uint bid) external returns (uint); - function cage() external; - function live() external returns (uint); -} - -interface FlapLike { - function kick(uint lot, uint bid) external returns (uint); - function cage(uint) external; - function live() external returns (uint); -} +pragma solidity >=0.6.12; interface VatLike { - function dai (address) external view returns (uint); - function sin (address) external view returns (uint); function heal(uint256) external; function hope(address) external; function nope(address) external; } contract Vow { + // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external auth { require(live == 1, "Vow/not-live"); wards[usr] = 1; } - function deny(address usr) external auth { wards[usr] = 0; } + 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, "Vow/not-authorized"); _; } // --- Data --- - VatLike public vat; // CDP Engine - FlapLike public flapper; // Surplus Auction House - FlopLike public flopper; // Debt Auction House + VatLike public immutable vat; - 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 + event Rely(address indexed usr); + event Deny(address indexed usr); + event Hope(address indexed usr); + event Nope(address indexed usr); + event Heal(uint256 rad); // --- Init --- - constructor(address vat_, address flapper_, address flopper_) public { - wards[msg.sender] = 1; - vat = VatLike(vat_); - flapper = FlapLike(flapper_); - flopper = FlopLike(flopper_); - vat.hope(flapper_); - live = 1; - } - - // --- Math --- - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - function min(uint x, uint y) internal pure returns (uint z) { - return x <= y ? x : y; - } + constructor(address vat_) public { + vat = VatLike(vat_); - // --- Administration --- - function file(bytes32 what, uint 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("Vow/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("Vow/file-unrecognized-param"); + wards[msg.sender] = 1; + emit Rely(msg.sender); } - // Push to debt-queue - function fess(uint tab) external auth { - sin[now] = add(sin[now], tab); - Sin = add(Sin, tab); + // --- Admin --- + function hope(address who) external auth { + vat.hope(who); + emit Hope(who); } - // Pop from debt-queue - function flog(uint era) external { - require(add(era, wait) <= now, "Vow/wait-not-finished"); - Sin = sub(Sin, sin[era]); - sin[era] = 0; + function nope(address who) external auth { + vat.nope(who); + emit Nope(who); } - // Debt settlement - function heal(uint rad) external { - require(rad <= vat.dai(address(this)), "Vow/insufficient-surplus"); - require(rad <= sub(sub(vat.sin(address(this)), Sin), Ash), "Vow/insufficient-debt"); + // --- Debt Settlement --- + function heal(uint256 rad) external { vat.heal(rad); - } - function kiss(uint rad) external { - require(rad <= Ash, "Vow/not-enough-ash"); - require(rad <= vat.dai(address(this)), "Vow/insufficient-surplus"); - Ash = sub(Ash, rad); - vat.heal(rad); - } - - // Debt auction - function flop() external returns (uint id) { - require(sump <= sub(sub(vat.sin(address(this)), Sin), Ash), "Vow/insufficient-debt"); - require(vat.dai(address(this)) == 0, "Vow/surplus-not-zero"); - Ash = add(Ash, sump); - id = flopper.kick(address(this), dump, sump); - } - // Surplus auction - function flap() external returns (uint id) { - require(vat.dai(address(this)) >= add(add(vat.sin(address(this)), bump), hump), "Vow/insufficient-surplus"); - require(sub(sub(vat.sin(address(this)), Sin), Ash) == 0, "Vow/debt-not-zero"); - id = flapper.kick(bump, 0); + emit Heal(rad); } - function cage() external auth { - require(live == 1, "Vow/not-live"); - live = 0; - Sin = 0; - Ash = 0; - flapper.cage(vat.dai(address(flapper))); - flopper.cage(); - vat.heal(min(vat.dai(address(this)), vat.sin(address(this)))); - } } From b1b418c133f76e129c8b7f47f95ab7b0b734b92b Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Tue, 28 Dec 2021 10:06:09 -0500 Subject: [PATCH 2/2] update cat to use bow --- src/test/vat.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/vat.t.sol b/src/test/vat.t.sol index efa0c140..9a4e4538 100644 --- a/src/test/vat.t.sol +++ b/src/test/vat.t.sol @@ -498,7 +498,7 @@ contract BiteTest is DSTest { vat.rely(address(jug)); cat = new Cat(address(vat)); - cat.file("vow", address(vow)); + cat.file("vow", address(bow)); cat.file("box", rad((10 ether) * MLN)); vat.rely(address(cat)); bow.rely(address(cat));