contract GlobalSettlement {
    // --- Auth ---
    mapping (address => uint) public authorizedAccounts;
    function addAuthorization(address account) external isAuthorized {
        authorizedAccounts[account] = 1;
        emit AddAuthorization(account);
    }
    function removeAuthorization(address account) external isAuthorized {
        authorizedAccounts[account] = 0;
        emit RemoveAuthorization(account);
    }
    modifier isAuthorized {
        require(authorizedAccounts[msg.sender] == 1, "GlobalSettlement/account-not-authorized");
        _;
    }

    // --- Data ---
    SAFEEngineLike            public safeEngine;
    LiquidationEngineLike    public liquidationEngine;
    AccountingEngineLike     public accountingEngine;
    OracleRelayerLike        public oracleRelayer;
    CoinSavingsAccountLike   public coinSavingsAccount;
    StabilityFeeTreasuryLike public stabilityFeeTreasury;

    uint256  public contractEnabled;
    uint256  public shutdownTime;
    uint256  public shutdownCooldown;
    uint256  public outstandingCoinSupply;                                      // [rad]

    mapping (bytes32 => uint256) public finalCoinPerCollateralPrice;            // [ray]
    mapping (bytes32 => uint256) public collateralShortfall;                    // [wad]
    mapping (bytes32 => uint256) public collateralTotalDebt;                    // [wad]
    mapping (bytes32 => uint256) public collateralCashPrice;                    // [ray]

    mapping (address => uint256)                      public coinBag;           // [wad]
    mapping (bytes32 => mapping (address => uint256)) public coinsUsedToRedeem; // [wad]

    // --- Events ---
    event AddAuthorization(address account);
    event RemoveAuthorization(address account);
    event ModifyParameters(bytes32 parameter, uint data);
    event ModifyParameters(bytes32 parameter, address data);
    event ShutdownSystem();
    event FreezeCollateralType(bytes32 collateralType, uint finalCoinPerCollateralPrice);
    event FastTrackAuction(bytes32 collateralType, uint256 auctionId, uint256 collateralTotalDebt);
    event ProcessSAFE(bytes32 collateralType, address safe, uint256 collateralShortfall);
    event FreeCollateral(bytes32 collateralType, address sender, int collateralAmount);
    event SetOutstandingCoinSupply(uint256 outstandingCoinSupply);
    event CalculateCashPrice(bytes32 collateralType, uint collateralCashPrice);
    event PrepareCoinsForRedeeming(address sender, uint coinBag);
    event RedeemCollateral(bytes32 collateralType, address sender, uint coinsAmount, uint collateralAmount);

    // --- Init ---
    constructor() public {
        authorizedAccounts[msg.sender] = 1;
        contractEnabled = 1;
        emit AddAuthorization(msg.sender);
    }

    // --- Math ---
    function addition(uint x, uint y) internal pure returns (uint z) {
        z = x + y;
        require(z >= x);
    }
    function subtract(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x);
    }
    function multiply(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x);
    }
    function minimum(uint x, uint y) internal pure returns (uint z) {
        return x <= y ? x : y;
    }
    uint constant WAD = 10 ** 18;
    uint constant RAY = 10 ** 27;
    function rmultiply(uint x, uint y) internal pure returns (uint z) {
        z = multiply(x, y) / RAY;
    }
    function rdivide(uint x, uint y) internal pure returns (uint z) {
        z = multiply(x, RAY) / y;
    }
    function wdivide(uint x, uint y) internal pure returns (uint z) {
        z = multiply(x, WAD) / y;
    }

    // --- Administration ---
    function modifyParameters(bytes32 parameter, address data) external isAuthorized {
        require(contractEnabled == 1, "GlobalSettlement/contract-not-enabled");
        if (parameter == "safeEngine") safeEngine = SAFEEngineLike(data);
        else if (parameter == "liquidationEngine") liquidationEngine = LiquidationEngineLike(data);
        else if (parameter == "accountingEngine") accountingEngine = AccountingEngineLike(data);
        else if (parameter == "oracleRelayer") oracleRelayer = OracleRelayerLike(data);
        else if (parameter == "coinSavingsAccount") coinSavingsAccount = CoinSavingsAccountLike(data);
        else if (parameter == "stabilityFeeTreasury") stabilityFeeTreasury = StabilityFeeTreasuryLike(data);
        else revert("GlobalSettlement/modify-unrecognized-parameter");
        emit ModifyParameters(parameter, data);
    }
    function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
        require(contractEnabled == 1, "GlobalSettlement/contract-not-enabled");
        if (parameter == "shutdownCooldown") shutdownCooldown = data;
        else revert("GlobalSettlement/modify-unrecognized-parameter");
        emit ModifyParameters(parameter, data);
    }

    // --- Settlement ---
    function shutdownSystem() external isAuthorized {
        require(contractEnabled == 1, "GlobalSettlement/contract-not-enabled");
        contractEnabled = 0;
        shutdownTime = now;
        safeEngine.disableContract();
        liquidationEngine.disableContract();
        // treasury must be disabled before AccountingEngine so that all surplus is gathered in one place
        if (address(stabilityFeeTreasury) != address(0)) {
          stabilityFeeTreasury.disableContract();
        }
        accountingEngine.disableContract();
        oracleRelayer.disableContract();
        if (address(coinSavingsAccount) != address(0)) {
          coinSavingsAccount.disableContract();
        }
        emit ShutdownSystem();
    }

    function freezeCollateralType(bytes32 collateralType) external {
        require(contractEnabled == 0, "GlobalSettlement/contract-still-enabled");
        require(finalCoinPerCollateralPrice[collateralType] == 0, "GlobalSettlement/final-collateral-price-already-defined");
        (collateralTotalDebt[collateralType],,,,,) = safeEngine.collateralTypes(collateralType);
        (OracleLike orcl,,) = oracleRelayer.collateralTypes(collateralType);
        // redemptionPrice is a ray, orcl returns a wad
        finalCoinPerCollateralPrice[collateralType] = wdivide(oracleRelayer.redemptionPrice(), uint(orcl.read()));
        emit FreezeCollateralType(collateralType, finalCoinPerCollateralPrice[collateralType]);
    }
    function fastTrackAuction(bytes32 collateralType, uint256 auctionId) external {
        require(finalCoinPerCollateralPrice[collateralType] != 0, "GlobalSettlement/final-collateral-price-not-defined");

        (address auctionHouse_,,)    = liquidationEngine.collateralTypes(collateralType);
        CollateralAuctionHouseLike collateralAuctionHouse = CollateralAuctionHouseLike(auctionHouse_);
        (, uint accumulatedRate,,,,) = safeEngine.collateralTypes(collateralType);

        uint bidAmount                    = collateralAuctionHouse.bidAmount(auctionId);
        uint collateralToSell             = collateralAuctionHouse.remainingAmountToSell(auctionId);
        address forgoneCollateralReceiver = collateralAuctionHouse.forgoneCollateralReceiver(auctionId);
        uint amountToRaise                = collateralAuctionHouse.amountToRaise(auctionId);

        safeEngine.createUnbackedDebt(address(accountingEngine), address(accountingEngine), amountToRaise);
        safeEngine.createUnbackedDebt(address(accountingEngine), address(this), bidAmount);
        safeEngine.approveSAFEModification(address(collateralAuctionHouse));
        collateralAuctionHouse.terminateAuctionPrematurely(auctionId);

        uint debt_ = amountToRaise / accumulatedRate;
        collateralTotalDebt[collateralType] = addition(collateralTotalDebt[collateralType], debt_);
        require(int(collateralToSell) >= 0 && int(debt_) >= 0, "GlobalSettlement/overflow");
        safeEngine.confiscateSAFECollateralAndDebt(collateralType, forgoneCollateralReceiver, address(this), address(accountingEngine), int(collateralToSell), int(debt_));
        emit FastTrackAuction(collateralType, auctionId, collateralTotalDebt[collateralType]);
    }
    function processSAFE(bytes32 collateralType, address safe) external {
        require(finalCoinPerCollateralPrice[collateralType] != 0, "GlobalSettlement/final-collateral-price-not-defined");
        (, uint accumulatedRate,,,,) = safeEngine.collateralTypes(collateralType);
        (uint safeCollateral, uint safeDebt) = safeEngine.safes(collateralType, safe);

        uint amountOwed = rmultiply(rmultiply(safeDebt, accumulatedRate), finalCoinPerCollateralPrice[collateralType]);
        uint minCollateral = minimum(safeCollateral, amountOwed);
        collateralShortfall[collateralType] = addition(
            collateralShortfall[collateralType],
            subtract(amountOwed, minCollateral)
        );

        require(minCollateral <= 2**255 && safeDebt <= 2**255, "GlobalSettlement/overflow");
        safeEngine.confiscateSAFECollateralAndDebt(
            collateralType,
            safe,
            address(this),
            address(accountingEngine),
            -int(minCollateral),
            -int(safeDebt)
        );

        emit ProcessSAFE(collateralType, safe, collateralShortfall[collateralType]);
    }
    function freeCollateral(bytes32 collateralType) external {
        require(contractEnabled == 0, "GlobalSettlement/contract-still-enabled");
        (uint safeCollateral, uint safeDebt) = safeEngine.safes(collateralType, msg.sender);
        require(safeDebt == 0, "GlobalSettlement/art-not-zero");
        require(safeCollateral <= 2**255, "GlobalSettlement/overflow");
        safeEngine.confiscateSAFECollateralAndDebt(
          collateralType,
          msg.sender,
          msg.sender,
          address(accountingEngine),
          -int(safeCollateral),
          0
        );
        emit FreeCollateral(collateralType, msg.sender, -int(safeCollateral));
    }
    function setOutstandingCoinSupply() external {
        require(contractEnabled == 0, "GlobalSettlement/contract-still-enabled");
        require(outstandingCoinSupply == 0, "GlobalSettlement/outstanding-coin-supply-not-zero");
        require(safeEngine.coinBalance(address(accountingEngine)) == 0, "GlobalSettlement/surplus-not-zero");
        require(now >= addition(shutdownTime, shutdownCooldown), "GlobalSettlement/shutdown-cooldown-not-finished");
        outstandingCoinSupply = safeEngine.globalDebt();
        emit SetOutstandingCoinSupply(outstandingCoinSupply);
    }
    function calculateCashPrice(bytes32 collateralType) external {
        require(outstandingCoinSupply != 0, "GlobalSettlement/outstanding-coin-supply-zero");
        require(collateralCashPrice[collateralType] == 0, "GlobalSettlement/collateral-cash-price-already-defined");

        (, uint accumulatedRate,,,,) = safeEngine.collateralTypes(collateralType);
        uint256 redemptionAdjustedDebt = rmultiply(
          rmultiply(collateralTotalDebt[collateralType], accumulatedRate), finalCoinPerCollateralPrice[collateralType]
        );
        collateralCashPrice[collateralType] = rdivide(
          multiply(subtract(redemptionAdjustedDebt, collateralShortfall[collateralType]), RAY), outstandingCoinSupply
        );
        emit CalculateCashPrice(collateralType, collateralCashPrice[collateralType]);
    }
    function prepareCoinsForRedeeming(uint256 coinAmount) external {
        require(outstandingCoinSupply != 0, "GlobalSettlement/outstanding-coin-supply-zero");
        safeEngine.transferInternalCoins(msg.sender, address(accountingEngine), multiply(coinAmount, RAY));
        coinBag[msg.sender] = addition(coinBag[msg.sender], coinAmount);
        emit PrepareCoinsForRedeeming(msg.sender, coinBag[msg.sender]);
    }
    function redeemCollateral(bytes32 collateralType, uint coinsAmount) external {
        require(collateralCashPrice[collateralType] != 0, "GlobalSettlement/collateral-cash-price-not-defined");
        uint collateralAmount = rmultiply(coinsAmount, collateralCashPrice[collateralType]);
        safeEngine.transferCollateral(
          collateralType,
          address(this),
          msg.sender,
          collateralAmount
        );
        coinsUsedToRedeem[collateralType][msg.sender] = addition(coinsUsedToRedeem[collateralType][msg.sender], coinsAmount);
        require(coinsUsedToRedeem[collateralType][msg.sender] <= coinBag[msg.sender], "GlobalSettlement/insufficient-bag-balance");
        emit RedeemCollateral(collateralType, msg.sender, coinsAmount, collateralAmount);
    }
}
/*
 This is the Global Settlement module. It is an
 involved, stateful process that takes place over nine steps.
 First we freeze the system and lock the prices for each collateral type.
 1. `shutdownSystem()`:
 - freezes user entrypoints
 - cancels collateral/surplus auctions
 - starts cooldown period
 2. `freezeCollateralType(collateralType)`:
 - set the final price for each collateralType, reading off the price feed
 We must process some system state before it is possible to calculate
 the final coin / collateral price. In particular, we need to determine:
 a. `collateralShortfall` (considers under-collateralised SAFEs)
 b. `outstandingCoinSupply` (after including system surplus / deficit)
 We determine (a) by processing all under-collateralised SAFEs with
 `processSAFE`
 3. `processSAFE(collateralType, safe)`:
 - cancels SAFE debt
 - any excess collateral remains
 - backing collateral taken
 We determine (b) by processing ongoing coin generating processes,
 i.e. auctions. We need to ensure that auctions will not generate any
 further coin income. In the two-way auction model this occurs when
 all auctions are in the reverse (`decreaseSoldAmount`) phase. There are two ways
 of ensuring this:
 4.  i) `shutdownCooldown`: set the cooldown period to be at least as long as the
 longest auction duration, which needs to be determined by the
 shutdown administrator.
 This takes a fairly predictable time to occur but with altered
 auction dynamics due to the now varying price of the system coin.
 ii) `fastTrackAuction`: cancel all ongoing auctions and seize the collateral.
 This allows for faster processing at the expense of more
 processing calls. This option allows coin holders to retrieve
 their collateral faster.
 `fastTrackAuction(collateralType, auctionId)`:
 - cancel individual collateral auctions in the `increaseBidSize` (forward) phase
 - retrieves collateral and returns coins to bidder
 - `decreaseSoldAmount` (reverse) phase auctions can continue normally
 Option (i), `shutdownCooldown`, is sufficient for processing the system
 settlement but option (ii), `fastTrackAuction`, will speed it up. Both options
 are available in this implementation, with `fastTrackAuction` being enabled on a
 per-auction basis.
 When a SAFE has been processed and has no debt remaining, the
 remaining collateral can be removed.
 5. `freeCollateral(collateralType)`:
 - remove collateral from the caller's SAFE
 - owner can call as needed
 After the processing period has elapsed, we enable calculation of
 the final price for each collateral type.
 6. `setOutstandingCoinSupply()`:
 - only callable after processing time period elapsed
 - assumption that all under-collateralised SAFEs are processed
 - fixes the total outstanding supply of coin
 - may also require extra SAFE processing to cover system surplus
 7. `calculateCashPrice(collateralType)`:
 - calculate `collateralCashPrice`
 - adjusts `collateralCashPrice` in the case of deficit / surplus
 At this point we have computed the final price for each collateral
 type and coin holders can now turn their coin into collateral. Each
 unit coin can claim a fixed basket of collateral.
 Coin holders must first `prepareCoinsForRedeeming` into a `coinBag`. Once prepared,
 coins cannot be transferred out of the bag. More coin can be added to a bag later.
 8. `prepareCoinsForRedeeming(coinAmount)`:
 - put some coins into a bag in order to 'redeemCollateral'. The bigger the bag, the more collateral the user can claim.
 9. `redeemCollateral(collateralType, collateralAmount)`:
 - exchange some coin from your bag for tokens from a specific collateral type
 - the amount of collateral available to redeem is limited by how big your bag is
 */
function shutdownSystem() external isAuthorized {
    require(contractEnabled == 1, "GlobalSettlement/contract-not-enabled");
    contractEnabled = 0;
    shutdownTime = now;
    safeEngine.disableContract();
    liquidationEngine.disableContract();
    // treasury must be disabled before AccountingEngine so that all surplus is gathered in one place
    if (address(stabilityFeeTreasury) != address(0)) {
      stabilityFeeTreasury.disableContract();
    }
    accountingEngine.disableContract();
    oracleRelayer.disableContract();
    if (address(coinSavingsAccount) != address(0)) {
      coinSavingsAccount.disableContract();
    }
    emit ShutdownSystem();
}

// --- Settlement ---