contract End {
    // --- 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, "End/not-authorized");
        _;
    }

    // --- Data ---
    VatLike  public vat;   // CDP Engine
    CatLike  public cat;
    DogLike  public dog;
    VowLike  public vow;   // Debt Engine
    PotLike  public pot;
    SpotLike public spot;

    uint256  public live;  // Active Flag
    uint256  public when;  // Time of cage                   [unix epoch time]
    uint256  public wait;  // Processing Cooldown Length             [seconds]
    uint256  public debt;  // Total outstanding dai following processing [rad]

    mapping (bytes32 => uint256) public tag;  // Cage price              [ray]
    mapping (bytes32 => uint256) public gap;  // Collateral shortfall    [wad]
    mapping (bytes32 => uint256) public Art;  // Total debt per ilk      [wad]
    mapping (bytes32 => uint256) public fix;  // Final cash price        [ray]

    mapping (address => uint256)                      public bag;  //    [wad]
    mapping (bytes32 => mapping (address => uint256)) public out;  //    [wad]

    // --- 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);

    event Cage();
    event Cage(bytes32 indexed ilk);
    event Snip(bytes32 indexed ilk, uint256 indexed id, address indexed usr, uint256 tab, uint256 lot, uint256 art);
    event Skip(bytes32 indexed ilk, uint256 indexed id, address indexed usr, uint256 tab, uint256 lot, uint256 art);
    event Skim(bytes32 indexed ilk, address indexed urn, uint256 wad, uint256 art);
    event Free(bytes32 indexed ilk, address indexed usr, uint256 ink);
    event Thaw();
    event Flow(bytes32 indexed ilk);
    event Pack(address indexed usr, uint256 wad);
    event Cash(bytes32 indexed ilk, address indexed usr, uint256 wad);

    // --- Init ---
    constructor() public {
        wards[msg.sender] = 1;
        live = 1;
        emit Rely(msg.sender);
    }

    // --- Math ---
    uint256 constant WAD = 10 ** 18;
    uint256 constant RAY = 10 ** 27;
    function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x + y;
        require(z >= 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 min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        return x <= y ? x : y;
    }
    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = mul(x, y) / RAY;
    }
    function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = mul(x, WAD) / y;
    }

    // --- Administration ---
    function file(bytes32 what, address data) external auth {
        require(live == 1, "End/not-live");
        if (what == "vat")  vat = VatLike(data);
        else if (what == "cat")   cat = CatLike(data);
        else if (what == "dog")   dog = DogLike(data);
        else if (what == "vow")   vow = VowLike(data);
        else if (what == "pot")   pot = PotLike(data);
        else if (what == "spot") spot = SpotLike(data);
        else revert("End/file-unrecognized-param");
        emit File(what, data);
    }
    function file(bytes32 what, uint256 data) external auth {
        require(live == 1, "End/not-live");
        if (what == "wait") wait = data;
        else revert("End/file-unrecognized-param");
        emit File(what, data);
    }

    // --- Settlement ---
    function cage() external auth {
        require(live == 1, "End/not-live");
        live = 0;
        when = block.timestamp;
        vat.cage();
        cat.cage();
        dog.cage();
        vow.cage();
        spot.cage();
        pot.cage();
        emit Cage();
    }

    function cage(bytes32 ilk) external {
        require(live == 0, "End/still-live");
        require(tag[ilk] == 0, "End/tag-ilk-already-defined");
        (Art[ilk],,,,) = vat.ilks(ilk);
        (PipLike pip,) = spot.ilks(ilk);
        // par is a ray, pip returns a wad
        tag[ilk] = wdiv(spot.par(), uint256(pip.read()));
        emit Cage(ilk);
    }

    function snip(bytes32 ilk, uint256 id) external {
        require(tag[ilk] != 0, "End/tag-ilk-not-defined");

        (address _clip,,,) = dog.ilks(ilk);
        ClipLike clip = ClipLike(_clip);
        (, uint256 rate,,,) = vat.ilks(ilk);
        (, uint256 tab, uint256 lot, address usr,,) = clip.sales(id);

        vat.suck(address(vow), address(vow),  tab);
        clip.yank(id);

        uint256 art = tab / rate;
        Art[ilk] = add(Art[ilk], art);
        require(int256(lot) >= 0 && int256(art) >= 0, "End/overflow");
        vat.grab(ilk, usr, address(this), address(vow), int256(lot), int256(art));
        emit Snip(ilk, id, usr, tab, lot, art);
    }

    function skip(bytes32 ilk, uint256 id) external {
        require(tag[ilk] != 0, "End/tag-ilk-not-defined");

        (address _flip,,) = cat.ilks(ilk);
        FlipLike flip = FlipLike(_flip);
        (, uint256 rate,,,) = vat.ilks(ilk);
        (uint256 bid, uint256 lot,,,, address usr,, uint256 tab) = flip.bids(id);

        vat.suck(address(vow), address(vow),  tab);
        vat.suck(address(vow), address(this), bid);
        vat.hope(address(flip));
        flip.yank(id);

        uint256 art = tab / rate;
        Art[ilk] = add(Art[ilk], art);
        require(int256(lot) >= 0 && int256(art) >= 0, "End/overflow");
        vat.grab(ilk, usr, address(this), address(vow), int256(lot), int256(art));
        emit Skip(ilk, id, usr, tab, lot, art);
    }

    function skim(bytes32 ilk, address urn) external {
        require(tag[ilk] != 0, "End/tag-ilk-not-defined");
        (, uint256 rate,,,) = vat.ilks(ilk);
        (uint256 ink, uint256 art) = vat.urns(ilk, urn);

        uint256 owe = rmul(rmul(art, rate), tag[ilk]);
        uint256 wad = min(ink, owe);
        gap[ilk] = add(gap[ilk], sub(owe, wad));

        require(wad <= 2**255 && art <= 2**255, "End/overflow");
        vat.grab(ilk, urn, address(this), address(vow), -int256(wad), -int256(art));
        emit Skim(ilk, urn, wad, art);
    }

    function free(bytes32 ilk) external {
        require(live == 0, "End/still-live");
        (uint256 ink, uint256 art) = vat.urns(ilk, msg.sender);
        require(art == 0, "End/art-not-zero");
        require(ink <= 2**255, "End/overflow");
        vat.grab(ilk, msg.sender, msg.sender, address(vow), -int256(ink), 0);
        emit Free(ilk, msg.sender, ink);
    }

    function thaw() external {
        require(live == 0, "End/still-live");
        require(debt == 0, "End/debt-not-zero");
        require(vat.dai(address(vow)) == 0, "End/surplus-not-zero");
        require(block.timestamp >= add(when, wait), "End/wait-not-finished");
        debt = vat.debt();
        emit Thaw();
    }
    function flow(bytes32 ilk) external {
        require(debt != 0, "End/debt-zero");
        require(fix[ilk] == 0, "End/fix-ilk-already-defined");

        (, uint256 rate,,,) = vat.ilks(ilk);
        uint256 wad = rmul(rmul(Art[ilk], rate), tag[ilk]);
        fix[ilk] = mul(sub(wad, gap[ilk]), RAY) / (debt / RAY);
        emit Flow(ilk);
    }

    function pack(uint256 wad) external {
        require(debt != 0, "End/debt-zero");
        vat.move(msg.sender, address(vow), mul(wad, RAY));
        bag[msg.sender] = add(bag[msg.sender], wad);
        emit Pack(msg.sender, wad);
    }
    function cash(bytes32 ilk, uint256 wad) external {
        require(fix[ilk] != 0, "End/fix-ilk-not-defined");
        vat.flux(ilk, address(this), msg.sender, rmul(wad, fix[ilk]));
        out[ilk][msg.sender] = add(out[ilk][msg.sender], wad);
        require(out[ilk][msg.sender] <= bag[msg.sender], "End/insufficient-bag-balance");
        emit Cash(ilk, msg.sender, wad);
    }
}
/*
 This is the `End` and it coordinates Global Settlement. This is an
 involved, stateful process that takes place over nine steps.
 
 First we freeze the system and lock the prices for each ilk.
 
 1. `cage()`:
 - freezes user entrypoints
 - cancels flop/flap auctions
 - starts cooldown period
 - stops pot drips
 
 2. `cage(ilk)`:
 - set the cage price for each `ilk`, reading off the price feed
 
 We must process some system state before it is possible to calculate
 the final dai / collateral price. In particular, we need to determine
 
 a. `gap`, the collateral shortfall per collateral type by
 considering under-collateralised CDPs.
 
 b. `debt`, the outstanding dai supply after including system
 surplus / deficit
 
 We determine (a) by processing all under-collateralised CDPs with
 `skim`:
 
 3. `skim(ilk, urn)`:
 - cancels CDP debt
 - any excess collateral remains
 - backing collateral taken
 
 We determine (b) by processing ongoing dai generating processes,
 i.e. auctions. We need to ensure that auctions will not generate any
 further dai income.
 
 In the two-way auction model (Flipper) this occurs when
 all auctions are in the reverse (`dent`) phase. There are two ways
 of ensuring this:
 
 4a. i) `wait`: set the cooldown period to be at least as long as the
 longest auction duration, which needs to be determined by the
 cage administrator.
 
 This takes a fairly predictable time to occur but with altered
 auction dynamics due to the now varying price of dai.
 
 ii) `skip`: cancel all ongoing auctions and seize the collateral.
 
 This allows for faster processing at the expense of more
 processing calls. This option allows dai holders to retrieve
 their collateral faster.
 
 `skip(ilk, id)`:
 - cancel individual flip auctions in the `tend` (forward) phase
 - retrieves collateral and debt (including penalty) to owner's CDP
 - returns dai to last bidder
 - `dent` (reverse) phase auctions can continue normally
 
 Option (i), `wait`, is sufficient (if all auctions were bidded at least
 once) for processing the system settlement but option (ii), `skip`,
 will speed it up. Both options are available in this implementation,
 with `skip` being enabled on a per-auction basis.
 
 In the case of the Dutch Auctions model (Clipper) they keep recovering
 debt during the whole lifetime and there isn't a max duration time
 guaranteed for the auction to end.
 So the way to ensure the protocol will not receive extra dai income is:
 
 4b. i) `snip`: cancel all ongoing auctions and seize the collateral.
 
 `snip(ilk, id)`:
 - cancel individual running clip auctions
 - retrieves remaining collateral and debt (including penalty)
 to owner's CDP
 
 When a CDP has been processed and has no debt remaining, the
 remaining collateral can be removed.
 
 5. `free(ilk)`:
 - remove collateral from the caller's CDP
 - owner can call as needed
 
 After the processing period has elapsed, we enable calculation of
 the final price for each collateral type.
 
 6. `thaw()`:
 - only callable after processing time period elapsed
 - assumption that all under-collateralised CDPs are processed
 - fixes the total outstanding supply of dai
 - may also require extra CDP processing to cover vow surplus
 
 7. `flow(ilk)`:
 - calculate the `fix`, the cash price for a given ilk
 - adjusts the `fix` in the case of deficit / surplus
 
 At this point we have computed the final price for each collateral
 type and dai holders can now turn their dai into collateral. Each
 unit dai can claim a fixed basket of collateral.
 
 Dai holders must first `pack` some dai into a `bag`. Once packed,
 dai cannot be unpacked and is not transferrable. More dai can be
 added to a bag later.
 
 8. `pack(wad)`:
 - put some dai into a bag in preparation for `cash`
 
 Finally, collateral can be obtained with `cash`. The bigger the bag,
 the more collateral can be released.
 
 9. `cash(ilk, wad)`:
 - exchange some dai from your bag for gems from a specific ilk
 - the number of gems is limited by how big your bag is
 */
function file(bytes32 what, address data) external auth {
    require(live == 1, "End/not-live");
    if (what == "vat")  vat = VatLike(data);
    else if (what == "cat")   cat = CatLike(data);
    else if (what == "dog")   dog = DogLike(data);
    else if (what == "vow")   vow = VowLike(data);
    else if (what == "pot")   pot = PotLike(data);
    else if (what == "spot") spot = SpotLike(data);
    else revert("End/file-unrecognized-param");
    emit File(what, data);
}

// --- Administration ---