User Defined Value Types

发布于:2025-07-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

本节是《Solidity by Example》的中文翻译与深入讲解,专为零基础或刚接触区块链开发的小白朋友打造。我们将通过“示例 + 解说 + 提示”的方式,带你逐步理解每一段 Solidity 代码的实际用途与背后的逻辑。

Solidity 是以太坊等智能合约平台使用的主要编程语言,就像写网页要用 HTML 和 JavaScript,写智能合约就需要会 Solidity。

如果你从没写过区块链代码也没关系,只要你了解一点点编程概念,比如“变量”“函数”“条件判断”,我们就能从最简单的例子开始,一步步建立你的 Solidity 编程思维。

User Defined Value Types

用户定义值类型

  • 什么是用户定义值类型(UDVT)?

    用户定义值类型(User Defined Value Types, UDVT)是 Solidity 0.8.8 及以上版本引入的一种特性,允许开发者基于内置值类型(如uint64)定义新的类型。

    • 比喻:想象你有一个普通的“数字”类型(比如 uint64),但你想给它取一个更有意义的名称,比如 Duration(持续时间)或 Timestamp(时间戳),以便代码更直观、更安全。UDVT 就像给数字穿上“标签”,让它们在语义上更清晰。
    • 语法:type NewType is BaseType;
      • NewType:新定义的类型名称。
      • BaseType:基础类型,如 uint64address 等。
    • UDVT 提供两种操作:
      • NewType.wrap(value):将基础类型值包装为用户定义类型。
      • NewType.unwrap(value):将用户定义类型解包为基础类型值。
  • 特点:

    • 类型安全:UDVT 限制变量的用法,防止混淆不同类型的参数(比如把时间戳误用为持续时间)。
    • 可读性:用 DurationTimestamp 代替 uint64,代码更直观。
    • 零成本抽象:UDVT 在底层仍使用基础类型(如 uint64),不会增加 Gas 成本。
    • 不可扩展:UDVT 仅为类型别名,不能添加新功能。
  • 用途:

    • 提高代码可读性:用语义化的名称(如 Duration)代替通用类型(如 uint64)。
    • 增强类型安全:防止参数顺序错误或类型混淆。
    • 简化复杂逻辑:结合库函数(如 LibClock),处理复合数据(如时间戳和持续时间的组合)。
// SPDX-License-Identifier: MIT
// 使用 MIT 许可证,允许自由使用、修改和分发代码。

pragma solidity ^0.8.26;
// 指定 Solidity 编译器版本,必须为 0.8.26 或更高(但低于 0.9.0)。

// Code copied from optimism
// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/dispute/lib/LibUDT.sol
// 代码来自 Optimism 项目,链接指向其 GitHub 仓库中的 LibUDT.sol 文件。
// Optimism 是一个以太坊二层解决方案,代码展示了 UDVT 在实际项目中的应用。

type Duration is uint64;
// 定义一个用户定义值类型 Duration,基于 uint64。
// 表示“持续时间”,语义上比 uint64 更清晰。
// 例如,Duration 表示时间段(如 3600 秒),而 uint64 只是普通数字。

type Timestamp is uint64;
// 定义一个用户定义值类型 Timestamp,基于 uint64。
// 表示“时间戳”,语义上表示某个时间点(如当前区块时间戳)。

type Clock is uint128;
// 定义一个用户定义值类型 Clock,基于 uint128。
// 表示一个复合类型,用于组合 Duration 和 Timestamp。
// uint128 提供足够空间存储两个 uint64(64 位 + 64 位 = 128 位)。

library LibClock {
    // 定义一个名为 LibClock 的库,包含操作 Clock 类型的函数。
    // 库是无状态的代码集合,函数为 internal 或 pure,不直接存储数据。

    function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_)
    {
        // 定义一个函数 wrap,接受 Duration 和 Timestamp 类型的参数,组合为 Clock 类型。
        // internal:仅限合约内部或继承的合约调用。
        // pure:不读取或修改区块链状态,链下计算,免费。
        // 返回 Clock 类型,表示组合后的值。

        assembly {
            // 使用内联汇编(assembly)进行低级操作。
            // data | Duration | Timestamp
            // bit  | 0 ... 63 | 64 ... 127
            // Clock 是一个 128 位值,低 64 位存储 Timestamp,高 64 位存储 Duration。
            clock_ := or(shl(0x40, _duration), _timestamp)
            // shl(0x40, _duration):将 _duration 左移 64 位(0x40 = 64),占据高 64 位。
            // or:将左移后的 _duration 和 _timestamp 按位或,合并为 128 位值。
            // 结果存储在 clock_ 中,返回 Clock 类型。
        }
    }

    function duration(Clock _clock) internal pure returns (Duration duration_)
    {
        // 定义一个函数 duration,从 Clock 类型中提取 Duration 值。
        // internal:仅限内部调用。
        // pure:不读取或修改区块链状态,免费。
        // 返回 Duration 类型。

        assembly {
            duration_ := shr(0x40, _clock)
            // shr(0x40, _clock):将 _clock 右移 64 位(0x40 = 64),提取高 64 位的 Duration 值。
            // 结果存储在 duration_ 中,返回 Duration 类型。
        }
    }

    function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_)
    {
        // 定义一个函数 timestamp,从 Clock 类型中提取 Timestamp 值。
        // internal:仅限内部调用。
        // pure:不读取或修改区块链状态,免费。
        // 返回 Timestamp 类型。

        assembly {
            timestamp_ := shr(0xC0, shl(0xC0, _clock))
            // shl(0xC0, _clock):将 _clock 左移 192 位(0xC0 = 192),清除高 64 位。
            // shr(0xC0, ...):再右移 192 位,提取低 64 位的 Timestamp 值。
            // 结果存储在 timestamp_ 中,返回 Timestamp 类型。
        }
    }
}

library LibClockBasic {
    // 定义一个名为 LibClockBasic 的库,展示不使用 UDVT 的版本。
    // 与 LibClock 功能相同,但使用普通类型 uint64 和 uint128。

    function wrap(uint64 _duration, uint64 _timestamp) internal pure returns (uint128 clock)
    {
        // 定义一个函数 wrap,接受两个 uint64 参数,组合为 uint128。
        // internal:仅限内部调用。
        // pure:不读取或修改区块链状态,免费。
        // 返回 uint128 类型。

        assembly {
            clock := or(shl(0x40, _duration), _timestamp)
            // 与 LibClock 的 wrap 逻辑相同,但参数和返回值是普通类型。
            // 低 64 位存储 _timestamp,高 64 位存储 _duration。
        }
    }
}

contract Examples {
    // 定义一个名为 Examples 的合约,展示 UDVT 和非 UDVT 的用法对比。

    function example_no_uvdt() external view {
        // 定义一个函数 example_no_uvdt,展示不使用 UDVT 的情况。
        // external:仅限外部调用(通过交易或合约调用)。
        // view:只读取区块链状态,不修改,链下调用免费。

        // Without UDVT
        // 不使用用户定义值类型
        uint128 clock;
        // 声明一个 uint128 类型的变量 clock,表示复合时间值。
        uint64 d = 1;
        // 声明一个 uint64 类型的变量 d,表示持续时间(1 秒)。
        uint64 t = uint64(block.timestamp);
        // 声明一个 uint64 类型的变量 t,表示当前区块时间戳(转换为 uint64)。
        clock = LibClockBasic.wrap(d, t);
        // 调用 LibClockBasic 的 wrap 函数,将 d 和 t 组合为 clock。
        // 正确顺序:d(持续时间)在高位,t(时间戳)在低位。

        // Oops! wrong order of inputs but still compiles
        // 错误!参数顺序错误,但代码仍能编译
        clock = LibClockBasic.wrap(t, d);
        // 错误调用:将 t(时间戳)作为第一个参数,d(持续时间)作为第二个参数。
        // 由于 LibClockBasic 使用普通类型 uint64,编译器无法检测这种错误。
        // 运行时会导致逻辑错误:时间戳被当作持续时间,持续时间被当作时间戳。
    }

    function example_uvdt() external view {
        // 定义一个函数 example_uvdt,展示使用 UDVT 的情况。
        // external:仅限外部调用。
        // view:只读取区块链状态,不修改,免费。

        // Turn value type into user defined value type
        // 将值类型转换为用户定义值类型
        Duration d = Duration.wrap(1);
        // 将 uint64 值 1 包装为 Duration 类型,表示持续时间 1 秒。
        Timestamp t = Timestamp.wrap(uint64(block.timestamp));
        // 将当前区块时间戳(block.timestamp)转换为 uint64,再包装为 Timestamp 类型。

        // Turn user defined value type back into primitive value type
        // 将用户定义值类型转换回基础值类型
        uint64 d_u64 = Duration.unwrap(d);
        // 将 Duration 类型解包为 uint64,得到原始值 1。
        uint64 t_u64 = Timestamp.unwrap(t);
        // 将 Timestamp 类型解包为 uint64,得到原始时间戳值。

        // LibClock example
        // LibClock 示例
        Clock clock = Clock.wrap(0);
        // 初始化 clock 为 0,包装为 Clock 类型。
        clock = LibClock.wrap(d, t);
        // 调用 LibClock 的 wrap 函数,将 d(Duration)和 t(Timestamp)组合为 clock。
        // 正确顺序:d(持续时间)在高位,t(时间戳)在低位。

        // Oops! wrong order of inputs
        // 错误!参数顺序错误
        // This will not compile
        // 这将无法编译
        // clock = LibClock.wrap(t, d);
        // 错误调用:将 t(Timestamp)作为第一个参数,d(Duration)作为第二个参数。
        // 由于 LibClock 的 wrap 函数要求第一个参数是 Duration,第二个是 Timestamp,
        // 编译器会检测到类型不匹配,报错,防止逻辑错误。
    }
}

代码包含三个部分:

  1. 用户定义值类型定义:定义了 DurationTimestampClock 三个类型,基于 uint64uint128
  2. 库合约 LibClock:使用 UDVT 操作复合数据(将 DurationTimestamp 组合为 Clock)。
  3. 库合约 LibClockBasic:不使用 UDVT 的对比版本,展示普通类型的用法。
  4. 合约 Examples:展示 UDVT 和非 UDVT 的用法对比,突出类型安全的优势。

UDVT 的本质

  • UDVT 是一种给基础类型(如 uint64)取“别名”的方式,增加语义和类型安全。
  • 比喻:想象你在超市买东西,uint64 像一个普通的水瓶,UDVT 给它贴上标签,比如“牛奶”或“果汁”。标签让代码更清楚(牛奶不是果汁),还能防止误用(不会把果汁当牛奶喝)。
  • 在底层,UDVT 仍使用基础类型(如 uint64),不增加 Gas 成本,但编译器会严格检查类型匹配。
  • 好处:
    • 可读性Durationuint64 更直观,表示“持续时间”。
    • 类型安全:防止参数顺序错误或类型混淆(如 DurationTimestamp 互换)。
    • 零成本:UDVT 是编译时特性,不增加运行时 Gas 成本。
    • 封装性:结合库(如 LibClock),方便操作复杂数据。

代码功能

  • 类型定义:
    • Duration:表示持续时间,基于 uint64
    • Timestamp:表示时间戳,基于 uint64
    • Clock:表示复合时间(持续时间 + 时间戳),基于 uint128
  • LibClock 库:
    • wrap(Duration, Timestamp):将 DurationTimestamp 组合为 Clock,高 64 位存 Duration,低 64 位存 Timestamp
    • duration(Clock):从 Clock 提取 Duration
    • timestamp(Clock):从 Clock 提取 Timestamp
    • 使用内联汇编(assembly)实现高效位操作。
  • LibClockBasic 库:
    • LibClock 功能相同,但使用普通类型(uint64uint128),没有类型安全保证。
  • Examples 合约:
    • example_no_uvdt:展示不使用 UDVT 的问题,参数顺序错误仍能编译,可能导致逻辑错误。
    • example_uvdt:展示使用 UDVT 的优势,参数顺序错误会导致编译错误,防止逻辑错误。

深入学习

  • 存储与 Gas:
    • UDVT 是编译时特性,底层使用基础类型(如 uint64),不增加 Gas 成本。
    • LibClock 使用内联汇编(assembly)进行位操作,效率高,但需谨慎确保正确性。
    • 读取(pureview 函数)链下免费,适合复杂逻辑。
  • 类型安全:
    • UDVT 防止参数混淆(如 DurationTimestamp),降低逻辑错误风险。
    • 结合库函数(如 LibClock),封装复杂操作,提高代码可维护性。
  • 安全性:
    • 内联汇编需仔细验证,防止位操作错误。
    • 确保 UDVT 的基础类型(如 uint64)适合用例(避免溢出)。
  • 复杂用例:
    • 时间管理:用 DurationTimestamp 管理区块链计时器。
    • 资产管理:用 Amount(基于 uint256)和 Account(基于 address)管理代币。

网站公告

今日签到

点亮在社区的每一天
去签到