MakerDAO Vat 合约分析

// SPDX-License-Identifier: AGPL-3.0-or-later

/// vat.sol -- Dai CDP database

// Copyright (C) 2018 Rain <rainbreak@riseup.net>
//
// 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;

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

/**
 * @title
 * @author
 * @notice
 * - `Vat` 合约中的 Dai 不是以 ERC-20 代币的形式存在的,而是作为系统内部的记账单位 `rad` 存在的
 *   这些 Dai 只在系统内部使用,不在市场上直接流通,设计原因如下:
 *      - 系统内部记账:Vat 合约使用 rad 作为记账单位,确保高精度(10^45)记录所有抵押品和债务。
 *      - 管理和操作:通过将 Dai 转换为内部记账单位,系统可以更容易地管理和操作抵押品和生成的 Dai。
 *      - 流通和流动性:在需要时,通过 DaiJoin 的 exit 操作,用户可以将 Vat 中的 Dai 转换回 ERC-20
 *        形式的 Dai,从而进入市场流通。
 */
contract Vat {
    // --- Auth ---
    mapping(address => uint256) public wards;

    function rely(address usr) external auth {
        require(live == 1, "Vat/not-live");
        wards[usr] = 1;
    }

    function deny(address usr) external auth {
        require(live == 1, "Vat/not-live");
        wards[usr] = 0;
    }

    modifier auth() {
        require(wards[msg.sender] == 1, "Vat/not-authorized");
        _;
    }

    mapping(address => mapping(address => uint256)) public can;

    function hope(address usr) external {
        can[msg.sender][usr] = 1;
    }

    function nope(address usr) external {
        can[msg.sender][usr] = 0;
    }

    function wish(address bit, address usr) internal view returns (bool) {
        return either(bit == usr, can[bit][usr] == 1);
    }

    // --- Data ---
    struct Ilk {
        // 抵押类型
        uint256 Art; // Total Normalised Debt     [wad] 总标准化债务 [wad = 10^18]
        uint256 rate; // Accumulated Rates         [ray] 累计费率 [ray = 10^27]
        uint256 spot; // Price with Safety Margin  [ray] 带有安全边际的价格 [ray = 10^27]
        uint256 line; // Debt Ceiling              [rad] 债务上限 [rad = 10^45]
        uint256 dust; // Urn Debt Floor            [rad] 债务下限 [rad = 10^45]
    }

    struct Urn {
        // 一个特殊的 Vault
        uint256 ink; // Locked Collateral  [wad = 10^18] 抵押品金额
        uint256 art; // Normalised Debt    [wad = 10^18] 标准化未偿还稳定币债务
    }

    // --- Mappings ---
    /// @notice 存储每种抵押品类型(ilk)的相关信息。
    mapping(bytes32 => Ilk) public ilks;
    /**
     * @notice 存储每种抵押品类型下,每个用户的 Vault 信息(即每个用户持有的特定抵押品的详细信息)。
     * - `bytes32` 抵押品类型标识符
     * - `address` 用户地址
     */
    mapping(bytes32 => mapping(address => Urn)) public urns;
    /// @notice 用于存储每种抵押品类型下,每个用户的抵押品余额。
    mapping(bytes32 => mapping(address => uint256)) public gem; // [wad = 10^18]
    mapping(address => uint256) public dai; // [rad = 10^45]
    mapping(address => uint256) public sin; // [rad = 10^45]

    uint256 public debt; // Total Dai Issued    [rad = 10^45] 总债务
    uint256 public vice; // Total Unbacked Dai  [rad = 10^45] 总不良债务
    uint256 public Line; // Total Debt Ceiling  [rad = 10^45] 总债务上限
    uint256 public live; // Active Flag

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

    // --- Math ---
    function _add(uint256 x, int256 y) internal pure returns (uint256 z) {
        z = x + uint256(y);
        require(y >= 0 || z <= x);
        require(y <= 0 || z >= x);
    }

    function _sub(uint256 x, int256 y) internal pure returns (uint256 z) {
        z = x - uint256(y);
        require(y <= 0 || z <= x);
        require(y >= 0 || z >= x);
    }

    function _mul(uint256 x, int256 y) internal pure returns (int256 z) {
        z = int256(x) * y;
        require(int256(x) >= 0);
        require(y == 0 || z / y == int256(x));
    }

    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 _mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        require(y == 0 || (z = x * y) / y == x);
    }

    // --- Administration ---
    /**
     * @notice 初始化新的抵押品类型(`ilk`)
     * @dev 该函数确保了一个 `ilk` 只能初始化一次,并在初始化时设置其初始费率
     * @dev `auth` 修饰符确保了这个函数的权限访问,只能具有授权的用户调用
     * @param ilk 抵押品类型
     */
    function init(bytes32 ilk) external auth {
        require(ilks[ilk].rate == 0, "Vat/ilk-already-init"); // 是否已经初始化,若已经初始化,直接回滚
        ilks[ilk].rate = 10 ** 27; // 初始化抵押品的费率为 1 ray [ray = 10^27]
    }

    /**
     * @notice 用于设置全局参数,例如系统的债务上限(`Line`)
     * @dev `auth` 修饰符确保了这个函数的权限访问,只能具有授权的用户调用
     * @param what 用于指定更新的参数名称,该函数中 `what` 可以是 `Line`  表示系统的总债务上限
     * @param data 指定 `what` 参数对应的新值,该函数中,`data` 是系统的总债务上限的具体数值
     */
    function file(bytes32 what, uint256 data) external auth {
        require(live == 1, "Vat/not-live");
        if (what == "Line") Line = data;
        else revert("Vat/file-unrecognized-param");
    }

    /**
     * @notice 用于设置特定抵押品类型的参数,例如现货价格(spot)、债务上限(line)和最小债务(dust)。
     * @param ilk 指定要更新的抵押品类型
     * @param what 指定要更新的具体参数名称,该函数中,可选值有 `spot`、`line`、`dust`,分别表示现货价格、债务上限和最小债务。
     * @param data 指定 `what` 参数对应的新值
     */
    function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
        require(live == 1, "Vat/not-live");
        if (what == "spot") ilks[ilk].spot = data;
        else if (what == "line") ilks[ilk].line = data;
        else if (what == "dust") ilks[ilk].dust = data;
        else revert("Vat/file-unrecognized-param");
    }

    function cage() external auth {
        live = 0;
    }

    // --- Fungibility --- (可互换性)
    /**
     * 修改用户抵押品的金额,admin
     * @param ilk 抵押品
     * @param usr 用户
     * @param wad 金额
     */
    function slip(bytes32 ilk, address usr, int256 wad) external auth {
        gem[ilk][usr] = _add(gem[ilk][usr], wad);
    }

    /**
     * 用户之间转移抵押品
     * @param ilk 抵押品
     * @param src 转出方
     * @param dst 转入到
     * @param wad 金额
     */
    function flux(bytes32 ilk, address src, address dst, uint256 wad) external {
        require(wish(src, msg.sender), "Vat/not-allowed");
        gem[ilk][src] = _sub(gem[ilk][src], wad);
        gem[ilk][dst] = _add(gem[ilk][dst], wad);
    }

    /**
     * 在用户之间转移稳定币
     * @param src 转出方用户地址
     * @param dst 转入到用户地址
     * @param rad 金额 [rad = 10^45]
     */
    function move(address src, address dst, uint256 rad) external {
        require(wish(src, msg.sender), "Vat/not-allowed");
        dai[src] = _sub(dai[src], rad);
        dai[dst] = _add(dai[dst], rad);
    }

    function either(bool x, bool y) internal pure returns (bool z) {
        assembly {
            z := or(x, y)
        }
    }

    function both(bool x, bool y) internal pure returns (bool z) {
        assembly {
            z := and(x, y)
        }
    }

    // --- CDP Manipulation --- (CDP 操纵)
    /**
     * 修改 Vault
     * @param i <=> ilk 资产类型
     * @param u <=> 对应的特殊 Vault
     * @param v <=> 授权检查地址(与抵押品操作相关)
     * @param w <=> Dai 债务的目标地址
     * @param dink 抵押品变化
     * @param dart 债务变化
     * @notice `dink` 为正值时,表示增加抵押品,抵押品从 `v` 地址转到 Vault,为负值,表示减少抵押品,从 Vault 转到 `v`
     * `dink` 为正值时,还需要检查 `v` 地址是否授权给 `msg.sender` 操作其抵押品
     * `dart` 为正值时,表示增加债务,生成的 Dai 发送到 `w` 地址。为负值时,表示偿还债务,`w` 地址的 Dai 被扣除
     * `dart` 为负值时,还需要检查 `w` 地址是否授权给 `msg.sender` 操作其 Dai
     */
    function frob(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external {
        // system is live
        require(live == 1, "Vat/not-live");

        Urn memory urn = urns[i][u];
        Ilk memory ilk = ilks[i];
        // ilk has been initialised
        require(ilk.rate != 0, "Vat/ilk-not-init");

        urn.ink = _add(urn.ink, dink);
        urn.art = _add(urn.art, dart);
        ilk.Art = _add(ilk.Art, dart);

        int256 dtab = _mul(ilk.rate, dart);
        uint256 tab = _mul(ilk.rate, urn.art);
        debt = _add(debt, dtab);

        // either debt has decreased, or debt ceilings are not exceeded 确保债务没有增加或没有超过债务上限
        require(either(dart <= 0, both(_mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded");
        // urn is either less risky than before, or it is safe 确保 Vault 的风险降低或仍然安全
        require(either(both(dart <= 0, dink >= 0), tab <= _mul(urn.ink, ilk.spot)), "Vat/not-safe");

        // urn is either more safe, or the owner consents 确保 Vault 更加安全,或者所有者同意
        require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");
        // collateral src consents 确保抵押品来源地址同意
        require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");
        // debt dst consents 确保债务目标地址同意
        require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");

        // urn has no debt, or a non-dusty amount 确保 Vault 没有债务,或者债务大于最低限额
        require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");

        gem[i][v] = _sub(gem[i][v], dink);
        dai[w] = _add(dai[w], dtab);

        urns[i][u] = urn;
        ilks[i] = ilk;
    }

    // --- CDP Fungibility ---

    /**
     * @notice 用于在两个 Vault 之间转移抵押品和债务。具体来说,它可以将一个 Vault 中的部分抵押品和债务拆分出来,转移到另一个
     * Vault 中
     * @param ilk 资产类型
     * @param src 抵押品来源 (源 Vault)
     * @param dst 债务目标 (目标 Vault)
     * @param dink 抵押品变化
     * @param dart 债务变化
     */
    function fork(bytes32 ilk, address src, address dst, int256 dink, int256 dart) external {
        Urn storage u = urns[ilk][src];
        Urn storage v = urns[ilk][dst];
        Ilk storage i = ilks[ilk];

        u.ink = _sub(u.ink, dink);
        u.art = _sub(u.art, dart);
        v.ink = _add(v.ink, dink);
        v.art = _add(v.art, dart);

        uint256 utab = _mul(u.art, i.rate);
        uint256 vtab = _mul(v.art, i.rate);

        // both sides consent
        require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");

        // both sides safe
        require(utab <= _mul(u.ink, i.spot), "Vat/not-safe-src");
        require(vtab <= _mul(v.ink, i.spot), "Vat/not-safe-dst");

        // both sides non-dusty
        require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
        require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
    }

    // --- CDP Confiscation ---

    /**
     * 清算 Vault
     * @param i 抵押品类型
     * @param u Vault 地址
     * @param v 抵押品的目标地址
     * @param w 债务的目标地址
     * @param dink 抵押品的变化量
     * @param dart 债务的变化量
     */
    function grab(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external auth {
        Urn storage urn = urns[i][u];
        Ilk storage ilk = ilks[i];

        urn.ink = _add(urn.ink, dink);
        urn.art = _add(urn.art, dart);
        ilk.Art = _add(ilk.Art, dart);

        int256 dtab = _mul(ilk.rate, dart);

        gem[i][v] = _sub(gem[i][v], dink);
        sin[w] = _sub(sin[w], dtab);
        vice = _sub(vice, dtab);
    }

    // --- Settlement ---
    /**
     * 抵消用户的 `sin`(sin:代表"扣押"或"坏"债务)和负债
     * @param rad 抵消的金额
     */
    function heal(uint256 rad) external {
        address u = msg.sender;
        sin[u] = _sub(sin[u], rad);
        dai[u] = _sub(dai[u], rad);
        vice = _sub(vice, rad);
        debt = _sub(debt, rad);
    }

    /**
     * 增加系统中的债务和不良债务,同时向特定用户分解 Dai
     * @param u 增加 `sin` 的用户地址
     * @param v 增加 Dai 的用户地址
     * @param rad 增加的金额
     */
    function suck(address u, address v, uint256 rad) external auth {
        sin[u] = _add(sin[u], rad);
        dai[v] = _add(dai[v], rad);
        vice = _add(vice, rad);
        debt = _add(debt, rad);
    }

    // --- Rates ---
    /**
     * 调整特定抵押品类型的费率,并更新系统中的 Dai 和债务
     * @param i 抵押品类型(ilk)
     * @param u 更新 Dai 余额的用户地址
     * @param rate 费率的变化,单位为 `ray` 10^27
     */
    function fold(bytes32 i, address u, int256 rate) external auth {
        require(live == 1, "Vat/not-live");
        Ilk storage ilk = ilks[i];
        ilk.rate = _add(ilk.rate, rate);
        int256 rad = _mul(ilk.Art, rate);
        dai[u] = _add(dai[u], rad);
        debt = _add(debt, rad);
    }
}