MakerDAO Abacus 合约分析

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

/// abaci.sol -- price decrease functions for auctions

// Copyright (C) 2020-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 Abacus {
    // 1st arg: initial price               [ray]       初始价格
    // 2nd arg: seconds since auction start [seconds]   拍卖开始后的秒数
    // returns: current auction price       [ray]       当前拍卖价格
    function price(uint256, uint256) external view returns (uint256);
}

/**
 * @title 线性下降合约
 * @author
 * @notice 用于计算线性下降的价格
 */
contract LinearDecrease is Abacus {
    // --- 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, "LinearDecrease/not-authorized");
        _;
    }

    // --- Data ---
    // 拍卖开始后价格归零后的秒数 [seconds]
    uint256 public tau; // Seconds after auction start when the price reaches zero [seconds]

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

    event File(bytes32 indexed what, uint256 data);

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

    // --- Administration ---
    /**
     *
     * @param what 要修改的数据,该函数 `what` 参数只能是 `tau`
     * @param data 将 `tau` 修改为 `data`
     */
    function file(bytes32 what, uint256 data) external auth {
        if (what == "tau") tau = data;
        else revert("LinearDecrease/file-unrecognized-param");
        emit File(what, data);
    }

    // --- Math ---
    uint256 constant RAY = 10 ** 27;

    function add(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);
    }

    /// @dev 固定点乘法函数,将两个定点数相乘,并且将结果缩放回来。
    /// @notice 该函数用于计算两个单位为 ray(10^27) 的数相乘
    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x * y;
        require(y == 0 || z / y == x);
        z = z / RAY;
    }

    // Price calculation when price is decreased linearly in proportion to time:
    // tau: The number of seconds after the start of the auction where the price will hit 0
    // top: Initial price
    // dur: current seconds since the start of the auction
    //
    // Returns y = top * ((tau - dur) / tau)
    //
    // Note the internal call to mul multiples by RAY, thereby ensuring that the rmul calculation
    // which utilizes top and tau (RAY values) is also a RAY value.
    /**
     * @dev 计算开始拍卖后对应秒数之后的价格
     * @param top 初始价格
     * @param dur 拍卖开始后的经历秒数
     * @return `y = top * ((tau - dur) / tau)
     * @notice RAY 内部调用了 mul 倍数,从而确保利用 top 和 tau(RAY 值)的 rmul 计算也是 RAY 值。
     */
    function price(uint256 top, uint256 dur) external view override returns (uint256) {
        // `tau` 拍卖开始后价格降至 0 所需的秒数 [seconds]
        if (dur >= tau) return 0;
        return rmul(top, mul(tau - dur, RAY) / tau);
    }
}

/**
 * @title 阶梯式指数下降合约
 * @author
 * @notice
 * - 此合约返回有效价格
 * - `cut` 和 `step` 值必须正确设置
 */
contract StairstepExponentialDecrease is Abacus {
    // --- 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, "StairstepExponentialDecrease/not-authorized");
        _;
    }

    // --- Data ---
    uint256 public step; // Length of time between price drops [seconds]    价格下跌间隔时间
    uint256 public cut; // Per-step multiplicative factor     [ray]         每单位间隔时间的乘数因子 [ray]

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

    event File(bytes32 indexed what, uint256 data);

    // --- Init ---
    //     this contract to return a valid price                    此合约返回有效价格
    // @notice: `cut` and `step` values must be correctly set for   `cut` 和 `step` 值必须正确设置
    constructor() public {
        wards[msg.sender] = 1;
        emit Rely(msg.sender);
    }

    // --- Administration ---
    /**
     * @dev 访问权限控制函数,修改系统参数
     * @param what 在本合约中,`what` 的参数有两个:`cut` 和 `step` ;若 `what == "cut"`,data 需要以 ray 为单位
     * @param data 将对应参数修改为的值
     */
    function file(bytes32 what, uint256 data) external auth {
        if (what == "cut") require((cut = data) <= RAY, "StairstepExponentialDecrease/cut-gt-RAY");
        else if (what == "step") step = data;
        else revert("StairstepExponentialDecrease/file-unrecognized-param");
        emit File(what, data);
    }

    // --- Math ---
    uint256 constant RAY = 10 ** 27;

    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x * y;
        require(y == 0 || z / y == x);
        z = z / RAY;
    }
    // optimized version from dss PR #78

    /**
     * @dev 固定点数的幂运算
     * @param x 底数
     * @param n 指数
     * @param b 基数,缩放结果
     * @return z 幂运算的结果
     */
    function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) {
        assembly {
            switch n
            case 0 { z := b }
            default {
                switch x
                case 0 { z := 0 }
                default {
                    switch mod(n, 2)
                    // 采取快速幂算法
                    case 0 { z := b }
                    default { z := x }
                    let half := div(b, 2) // for rounding.
                    for { n := div(n, 2) } n { n := div(n, 2) } {
                        let xx := mul(x, x)
                        if shr(128, x) { revert(0, 0) }
                        let xxRound := add(xx, half)
                        if lt(xxRound, xx) { revert(0, 0) }
                        x := div(xxRound, b)
                        if mod(n, 2) {
                            let zx := mul(z, x)
                            if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
                            let zxRound := add(zx, half)
                            if lt(zxRound, zx) { revert(0, 0) }
                            z := div(zxRound, b)
                        }
                    }
                }
            }
        }
    }

    // top: initial price
    // dur: seconds since the auction has started
    // step: seconds between a price drop
    // cut: cut encodes the percentage to decrease per step.
    //   For efficiency, the values is set as (1 - (% value / 100)) * RAY
    //   So, for a 1% decrease per step, cut would be (1 - 0.01) * RAY
    //
    // returns: top * (cut ^ dur)
    //
    //
    /**
     * @notice
     * - 计算开始拍卖后对应秒数之后的价格
     * - step:一次降低价格的间隔秒数
     * - cut:每单位价格下降间隔秒数减少的百分比
     *      为了提高效率,值设置为 (1 -(% value / 100)) * RAY
     *      因此,每 step 减少 1%,cut 将是 (1- 0.01) * RAY
     * @param top 初始价格
     * @param dur 拍卖开始后经历的秒数
     * @return `top * (cut ^ dur)`
     */
    function price(uint256 top, uint256 dur) external view override returns (uint256) {
        return rmul(top, rpow(cut, dur / step, RAY));
        // top * cut * cut * cut …… (cut number = dur / strp)
    }
}

// While an equivalent function can be obtained by setting step = 1 in StairstepExponentialDecrease,
// this continous (i.e. per-second) exponential decrease has be implemented as it is more gas-efficient
// than using the stairstep version with step = 1 (primarily due to 1 fewer SLOAD per price calculation).
/**
 * @title 指数下降合约
 * @author
 * @notice
 * - 虽然可以通过在 StairstepExponentialDecrease 中设置 `step = 1` 来获得等效函数,
 *   但这种连续(即每秒)指数下降已被实现,因为它比使用 `step = 1` 的阶梯版本更节省 gas
 *   (主要是因为每次价格计算的 SLOAD 少 1 个)。
 * - `cut` 和 `step` 值必须正确设置
 * - 此合约返回有效价格
 */
contract ExponentialDecrease is Abacus {
    // --- 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, "ExponentialDecrease/not-authorized");
        _;
    }

    // --- Data ---
    uint256 public cut; // Per-second multiplicative factor [ray] 每秒的乘数因子 [ray]

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

    event File(bytes32 indexed what, uint256 data);

    // --- Init ---
    // @notice: `cut` value must be correctly set for   `cut`的值必须正确设置
    //     this contract to return a valid price        此合约返回有效价格
    constructor() public {
        wards[msg.sender] = 1;
        emit Rely(msg.sender);
    }

    // --- Administration ---
    /**
     * @dev 访问权限控制函数,修改系统参数
     * @param what 在本合约中,`what` 的参数只能是`cut`
     * @param data 将对应参数修改为的值,在本合约中,`data`必须以 ray 为单位
     */
    function file(bytes32 what, uint256 data) external auth {
        if (what == "cut") require((cut = data) <= RAY, "ExponentialDecrease/cut-gt-RAY");
        else revert("ExponentialDecrease/file-unrecognized-param");
        emit File(what, data);
    }

    // --- Math ---
    uint256 constant RAY = 10 ** 27;

    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x * y;
        require(y == 0 || z / y == x);
        z = z / RAY;
    }
    // optimized version from dss PR #78

    /**
     * @dev 固定点数的幂运算
     * @param x 底数
     * @param n 指数
     * @param b 基数,缩放结果
     * @return z 幂运算的结果
     */
    function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) {
        assembly {
            switch n
            case 0 { z := b }
            default {
                switch x
                case 0 { z := 0 }
                default {
                    switch mod(n, 2)
                    case 0 { z := b }
                    default { z := x }
                    let half := div(b, 2) // for rounding.
                    for { n := div(n, 2) } n { n := div(n, 2) } {
                        let xx := mul(x, x)
                        if shr(128, x) { revert(0, 0) }
                        let xxRound := add(xx, half)
                        if lt(xxRound, xx) { revert(0, 0) }
                        x := div(xxRound, b)
                        if mod(n, 2) {
                            let zx := mul(z, x)
                            if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
                            let zxRound := add(zx, half)
                            if lt(zxRound, zx) { revert(0, 0) }
                            z := div(zxRound, b)
                        }
                    }
                }
            }
        }
    }

    // top: initial price
    // dur: seconds since the auction has started
    // cut: cut encodes the percentage to decrease per second.
    //   For efficiency, the values is set as (1 - (% value / 100)) * RAY
    //   So, for a 1% decrease per second, cut would be (1 - 0.01) * RAY
    //
    // returns: top * (cut ^ dur)
    //
    /**
     * @notice
     * - 计算开始拍卖后对应秒数之后的价格
     * - cut:每秒数减少的百分比
     *      为了提高效率,值设置为 (1 -(% value / 100)) * RAY
     *      因此,每秒减少 1%,cut 将是 (1- 0.01) * RAY
     * @param top 初始价格
     * @param dur 拍卖开始后经历的秒数
     * @return `top * (cut ^ dur)`
     */
    function price(uint256 top, uint256 dur) external view override returns (uint256) {
        return rmul(top, rpow(cut, dur, RAY));
    }
}